mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['', 'src/binary-exploitation/ios-exploiting/ios-physical-uaf
This commit is contained in:
parent
93584888f3
commit
7944763a06
@ -1,99 +1,99 @@
|
|||||||
# iOS Physical Use-After-Free via IOSurface
|
# iOS Physical Use After Free przez IOSurface
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
|
|
||||||
## Physical use-after-free
|
## Fizyczny use-after-free
|
||||||
|
|
||||||
To jest podsumowanie wpisu z [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html); dodatkowe informacje o exploitach wykorzystujących tę technikę można znaleźć w [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
To podsumowanie wpisu z [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html). Dalsze informacje o exploitach wykorzystujących tę technikę można znaleźć w [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
||||||
|
|
||||||
### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
### Zarządzanie pamięcią w XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
||||||
|
|
||||||
Przestrzeń adresowa pamięci wirtualnej dla procesów użytkownika na iOS obejmuje zakres od **0x0 do 0x8000000000**. Te adresy nie odpowiadają bezpośrednio pamięci fizycznej. Zamiast tego **kernel** używa **page tables** do tłumaczenia adresów wirtualnych na rzeczywiste **adresy fizyczne**.
|
Przestrzeń adresowa pamięci wirtualnej dla procesów użytkownika na iOS rozciąga się od **0x0 do 0x8000000000**. Jednak te adresy nie mapują się bezpośrednio na pamięć fizyczną. Zamiast tego jądro używa **tablic stron** do tłumaczenia adresów wirtualnych na rzeczywiste **adresy fizyczne**.
|
||||||
|
|
||||||
#### Levels of Page Tables in iOS
|
#### Poziomy tablic stron w iOS
|
||||||
|
|
||||||
Page tables są zorganizowane hierarchicznie w trzech poziomach:
|
Tablice stron są zorganizowane hierarchicznie w trzech poziomach:
|
||||||
|
|
||||||
1. **L1 Page Table (Level 1)**:
|
1. **L1 Tablica stron (Poziom 1)**:
|
||||||
* Każdy wpis reprezentuje duży zakres pamięci wirtualnej.
|
* Każdy wpis tutaj reprezentuje dużą przestrzeń pamięci wirtualnej.
|
||||||
* Pokrywa **0x1000000000 bytes** (czyli **256 GB**) pamięci wirtualnej.
|
* Pokrywa **0x1000000000 bajtów** (czyli **256 GB**) pamięci wirtualnej.
|
||||||
2. **L2 Page Table (Level 2)**:
|
2. **L2 Tablica stron (Poziom 2)**:
|
||||||
* Wpis tutaj reprezentuje mniejszy region pamięci wirtualnej, konkretnie **0x2000000 bytes** (32 MB).
|
* Wpis tutaj reprezentuje mniejszy obszar pamięci wirtualnej, konkretnie **0x2000000 bajtów** (32 MB).
|
||||||
* Wpis w L1 może wskazywać na tabelę L2, jeśli nie może samodzielnie zmapować całego regionu.
|
* Wpis L1 może wskazywać na tablicę L2, jeśli nie może odwzorować całego regionu samodzielnie.
|
||||||
3. **L3 Page Table (Level 3)**:
|
3. **L3 Tablica stron (Poziom 3)**:
|
||||||
* To najdokładniejszy poziom, gdzie każdy wpis mapuje pojedynczą stronę pamięci o rozmiarze **4 KB**.
|
* To najdrobniejszy poziom, gdzie każdy wpis mapuje pojedynczą stronę pamięci **4 KB**.
|
||||||
* Wpis w L2 może wskazywać na tabelę L3, jeśli wymagana jest większa granularność.
|
* Wpis L2 może wskazywać na tablicę L3, jeśli potrzebna jest większa szczegółowość.
|
||||||
|
|
||||||
#### Mapping Virtual to Physical Memory
|
#### Mapowanie wirtualnego na fizyczne
|
||||||
|
|
||||||
* **Direct Mapping (Block Mapping)**:
|
* **Mapowanie bezpośrednie (Block Mapping)**:
|
||||||
* Niektóre wpisy w page table bezpośrednio **mapują zakres adresów wirtualnych** na ciągły zakres adresów fizycznych (jak skrót).
|
* Niektóre wpisy w tablicy stron bezpośrednio **mapują zakres adresów wirtualnych** na ciągły zakres adresów fizycznych (jak skrót).
|
||||||
* **Pointer to Child Page Table**:
|
* **Wskaźnik do potomnej tablicy stron**:
|
||||||
* Jeśli potrzebna jest większa kontrola, wpis na jednym poziomie (np. L1) może wskazywać na **child page table** na następnym poziomie (np. L2).
|
* Jeśli potrzebna jest większa kontrola, wpis na jednym poziomie (np. L1) może wskazywać na **potomną tablicę stron** na następnym poziomie (np. L2).
|
||||||
|
|
||||||
#### Example: Mapping a Virtual Address
|
#### Przykład: mapowanie adresu wirtualnego
|
||||||
|
|
||||||
Załóżmy, że próbuje się uzyskać dostęp do adresu wirtualnego **0x1000000000**:
|
Powiedzmy, że próbujesz uzyskać dostęp do adresu wirtualnego **0x1000000000**:
|
||||||
|
|
||||||
1. **L1 Table**:
|
1. **Tablica L1**:
|
||||||
* Kernel sprawdza wpis w tabeli L1 odpowiadający temu adresowi wirtualnemu. Jeśli ma **pointer to an L2 page table**, przechodzi do tej L2.
|
* Jądro sprawdza wpis w tablicy L1 odpowiadający temu adresowi wirtualnemu. Jeśli ma on **wskaźnik do tablicy L2**, przechodzi do tej tablicy L2.
|
||||||
2. **L2 Table**:
|
2. **Tablica L2**:
|
||||||
* Kernel sprawdza tabelę L2 w poszukiwaniu bardziej szczegółowego mapowania. Jeśli wpis wskazuje na **L3 page table**, idzie dalej.
|
* Jądro sprawdza tablicę L2 w poszukiwaniu bardziej szczegółowego mapowania. Jeśli ten wpis wskazuje na **tablicę L3**, przechodzi tam.
|
||||||
3. **L3 Table**:
|
3. **Tablica L3**:
|
||||||
* Kernel odczytuje końcowy wpis L3, który wskazuje na **adres fizyczny** właściwej strony pamięci.
|
* Jądro odczytuje końcowy wpis L3, który wskazuje na **adres fizyczny** właściwej strony pamięci.
|
||||||
|
|
||||||
#### Example of Address Mapping
|
#### Przykład mapowania adresów
|
||||||
|
|
||||||
Jeśli zapiszesz adres fizyczny **0x800004000** w pierwszym indeksie tabeli L2, to:
|
Jeśli wpiszesz adres fizyczny **0x800004000** do pierwszego indeksu tablicy L2, to:
|
||||||
|
|
||||||
* Adresy wirtualne od **0x1000000000** do **0x1002000000** będą mapowane na adresy fizyczne od **0x800004000** do **0x802004000**.
|
* Adresy wirtualne od **0x1000000000** do **0x1002000000** będą mapowane na adresy fizyczne od **0x800004000** do **0x802004000**.
|
||||||
* To jest **block mapping** na poziomie L2.
|
* To jest **mapowanie blokowe** na poziomie L2.
|
||||||
|
|
||||||
Alternatywnie, jeśli wpis L2 wskazuje na tabelę L3:
|
Alternatywnie, jeśli wpis L2 wskazuje na tablicę L3:
|
||||||
|
|
||||||
* Każda strona 4 KB w zakresie wirtualnym **0x1000000000 -> 0x1002000000** byłaby mapowana przez indywidualne wpisy w tabeli L3.
|
* Każda strona 4 KB w zakresie wirtualnym **0x1000000000 -> 0x1002000000** byłaby mapowana przez indywidualne wpisy w tablicy L3.
|
||||||
|
|
||||||
### Physical use-after-free
|
### Fizyczny use-after-free
|
||||||
|
|
||||||
Physical use-after-free (UAF) występuje, gdy:
|
Fizyczny use-after-free (UAF) występuje, gdy:
|
||||||
|
|
||||||
1. Proces **alokuje** pamięć jako **readable and writable**.
|
1. Proces **alokuje** pewną pamięć jako **do odczytu i zapisu**.
|
||||||
2. **page tables** są zaktualizowane, aby zmapować tę pamięć na określony adres fizyczny dostępny dla procesu.
|
2. **Tablice stron** zostają zaktualizowane, aby zmapować tę pamięć na konkretny adres fizyczny, do którego proces ma dostęp.
|
||||||
3. Proces **dealokuje** (zwalnia) tę pamięć.
|
3. Proces **dealokuje** (zwalnia) pamięć.
|
||||||
4. Jednak z powodu **buga** kernel **zapomina usunąć mapowanie** z page tables, mimo że odpowiednia pamięć fizyczna została oznaczona jako wolna.
|
4. Jednak z powodu **błędu** jądro **zapomina usunąć mapowanie** z tablic stron, chociaż oznacza odpowiadającą pamięć fizyczną jako wolną.
|
||||||
5. Kernel może potem **realokować tę „zwolnioną” pamięć fizyczną** do innych celów, np. danych jądra.
|
5. Jądro może wtedy **ponownie przydzielić tę „zwolnioną” pamięć fizyczną** do innych celów, np. danych jądra.
|
||||||
6. Ponieważ mapowanie nie zostało usunięte, proces dalej może **czytać i zapisywać** tę pamięć fizyczną.
|
6. Ponieważ mapowanie nie zostało usunięte, proces wciąż może **odczytywać i zapisywać** tę pamięć fizyczną.
|
||||||
|
|
||||||
To oznacza, że proces może uzyskać dostęp do **stron pamięci jądra**, które mogą zawierać wrażliwe dane lub struktury, co potencjalnie pozwala atakującemu **manipulować pamięcią kernela**.
|
To oznacza, że proces może uzyskać dostęp do **stron pamięci jądra**, które mogą zawierać wrażliwe dane lub struktury, co potencjalnie pozwala atakującemu **manipulować pamięcią jądra**.
|
||||||
|
|
||||||
### IOSurface Heap Spray
|
### IOSurface Heap Spray
|
||||||
|
|
||||||
Skoro atakujący nie może kontrolować, które konkretne strony kernela zostaną przydzielone do zwolnionej pamięci, używa techniki zwanej **heap spray**:
|
Ponieważ atakujący nie może kontrolować, które konkretne strony jądra zostaną przydzielone do zwolnionej pamięci, używają techniki zwanej **heap spray**:
|
||||||
|
|
||||||
1. Atakujący **tworzy dużą liczbę obiektów IOSurface** w pamięci kernela.
|
1. Atakujący **tworzy dużą liczbę obiektów IOSurface** w pamięci jądra.
|
||||||
2. Każdy obiekt IOSurface zawiera **magic value** w jednym ze swoich pól, co ułatwia jego identyfikację.
|
2. Każdy obiekt IOSurface zawiera **magic value** w jednym ze swoich pól, co ułatwia identyfikację.
|
||||||
3. Skanują zwolnione strony, aby sprawdzić, czy któryś z tych obiektów IOSurface trafił na zwolnioną stronę.
|
3. Skanują one **zwolnione strony**, aby sprawdzić, czy któryś z tych obiektów IOSurface trafił na zwolnioną stronę.
|
||||||
4. Gdy znajdą obiekt IOSurface na zwolnionej stronie, mogą go użyć do **odczytu i zapisu pamięci kernela**.
|
4. Gdy znajdą obiekt IOSurface na zwolnionej stronie, mogą go użyć do **odczytu i zapisu pamięci jądra**.
|
||||||
|
|
||||||
Więcej informacji: [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
Więcej informacji na ten temat w [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> Pamiętaj, że urządzenia iOS 16+ (A12+) wprowadzają hardware mitigations (jak PPL lub SPTM), które znacznie utrudniają techniki physical UAF.
|
> Należy mieć świadomość, że urządzenia iOS 16+ (A12+) wprowadzają sprzętowe mechanizmy utrudniające (takie jak PPL czy SPTM), które znacząco ograniczają skuteczność technik fizycznego UAF.
|
||||||
> PPL wymusza ścisłe zabezpieczenia MMU na stronach związanych z code signing, entitlements i wrażliwymi danymi kernela, więc nawet jeśli strona zostanie ponownie użyta, zapisy z userland lub skompromitowanego kodu kernela do stron chronionych przez PPL są blokowane.
|
> PPL wymusza ścisłe ochrony MMU na stronach związanych z podpisywaniem kodu, uprawnieniami i wrażliwymi danymi jądra, więc nawet jeśli strona zostanie ponownie użyta, zapisy z userlandu lub skompromitowanego kodu jądra do stron chronionych przez PPL są blokowane.
|
||||||
> Secure Page Table Monitor (SPTM) rozszerza PPL, umacniając sam proces aktualizacji page tables. Zapewnia, że nawet uprzywilejowany kod kernela nie może cicho przemapować zwolnionych stron ani manipulować mapowaniami bez przejścia przez bezpieczne kontrole.
|
> Secure Page Table Monitor (SPTM) rozszerza PPL, wzmacniając same aktualizacje tablic stron. Zapewnia, że nawet uprzywilejowany kod jądra nie może cicho remapować zwolnionych stron ani manipulować mapowaniami bez przejścia przez bezpieczne sprawdzenia.
|
||||||
> KTRR (Kernel Text Read-Only Region) blokuje sekcję kodu kernela jako read-only po uruchomieniu systemu. To zapobiega wszelkim modyfikacjom kodu kernela w czasie działania, zamykając istotny wektor ataku, na którym często opierają się exploity physical UAF.
|
> KTRR (Kernel Text Read-Only Region) blokuje sekcję kodu jądra jako tylko do odczytu po uruchomieniu. To uniemożliwia runtime’owe modyfikacje kodu jądra, zamykając istotny wektor ataku, na którym opierają się często fizyczne UAF.
|
||||||
> Ponadto alokacje `IOSurface` są mniej przewidywalne i trudniej je zamapować do regionów dostępnych dla użytkownika, co sprawia, że trik z „skanowaniem magic value” jest znacznie mniej niezawodny. `IOSurface` jest teraz również chroniony przez entitlements i ograniczenia sandboxu.
|
> Ponadto alokacje `IOSurface` są mniej przewidywalne i trudniejsze do zmapowania do regionów dostępnych dla użytkownika, co sprawia, że trik ze skanowaniem „magic value” jest znacznie mniej niezawodny. `IOSurface` jest teraz również chronione uprawnieniami i ograniczeniami sandboxu.
|
||||||
|
|
||||||
### Step-by-Step Heap Spray Process
|
### Krok po kroku procesu Heap Spray
|
||||||
|
|
||||||
1. **Spray IOSurface Objects**: Atakujący tworzy wiele obiektów IOSurface z specjalnym identyfikatorem ("magic value").
|
1. **Spray obiektów IOSurface**: Atakujący tworzy wiele obiektów IOSurface ze specjalnym identyfikatorem (wartością "magic").
|
||||||
2. **Scan Freed Pages**: Sprawdzają, czy któryś z obiektów został przydzielony na zwolnionej stronie.
|
2. **Skanowanie zwolnionych stron**: Sprawdzają, czy któryś z obiektów został przydzielony na zwolnionej stronie.
|
||||||
3. **Read/Write Kernel Memory**: Manipulując polami obiektu IOSurface, uzyskują możliwość wykonywania **arbitrary reads and writes** w pamięci kernela. To pozwala im:
|
3. **Odczyt/Zapis pamięci jądra**: Poprzez manipulację polami obiektu IOSurface uzyskują możliwość wykonania **dowolnych odczytów i zapisów** w pamięci jądra. Pozwala to:
|
||||||
* Użyć jednego pola do **odczytu dowolnej 32-bit wartości** w pamięci kernela.
|
* Użyć jednego pola do **odczytu dowolnej 32-bitowej wartości** w pamięci jądra.
|
||||||
* Użyć innego pola do **zapisu 64-bit wartości**, uzyskując stabilny **kernel read/write primitive**.
|
* Użyć innego pola do **zapisania 64-bitowych wartości**, osiągając stabilny **prymityw odczytu/zapisu jądra**.
|
||||||
|
|
||||||
Generate IOSurface objects with the magic value IOSURFACE\_MAGIC to later search for:
|
Generuj obiekty IOSurface z magiczną wartością IOSURFACE\_MAGIC, aby później je wyszukać:
|
||||||
```c
|
```c
|
||||||
void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
|
void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
|
||||||
if (*nClients >= 0x4000) return;
|
if (*nClients >= 0x4000) return;
|
||||||
@ -114,7 +114,7 @@ io_connect_t id = result.surface_id;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Wyszukaj obiekty **`IOSurface`** w jednej zwolnionej stronie fizycznej:
|
Wyszukaj obiekty **`IOSurface`** na jednej zwolnionej fizycznej stronie:
|
||||||
```c
|
```c
|
||||||
int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
|
int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
|
||||||
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
|
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
|
||||||
@ -148,25 +148,25 @@ free(surfaceIDs);
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### Uzyskiwanie odczytu/zapisu jądra za pomocą IOSurface
|
### Osiągnięcie odczytu/zapisu jądra przy użyciu IOSurface
|
||||||
|
|
||||||
Po przejęciu kontroli nad obiektem IOSurface w pamięci jądra (zmapowanym do zwolnionej strony fizycznej dostępnej z userspace), możemy go użyć do **dowolnych operacji odczytu i zapisu w jądrze**.
|
Po uzyskaniu kontroli nad obiektem IOSurface w pamięci jądra (zmapowanym do zwolnionej strony fizycznej dostępnej z userspace), możemy go użyć do **dowolnych operacji odczytu i zapisu w jądrze**.
|
||||||
|
|
||||||
**Kluczowe pola w IOSurface**
|
**Key Fields in IOSurface**
|
||||||
|
|
||||||
Obiekt IOSurface ma dwa kluczowe pola:
|
The IOSurface object has two crucial fields:
|
||||||
|
|
||||||
1. **Use Count Pointer**: Umożliwia **32-bitowy odczyt**.
|
1. **Use Count Pointer**: Pozwala na **32-bitowy odczyt**.
|
||||||
2. **Indexed Timestamp Pointer**: Umożliwia **64-bitowy zapis**.
|
2. **Indexed Timestamp Pointer**: Pozwala na **64-bitowy zapis**.
|
||||||
|
|
||||||
Przez nadpisanie tych wskaźników przekierowujemy je na dowolne adresy w pamięci jądra, co udostępnia możliwości odczytu/zapisu.
|
Nadpisując te wskaźniki, przekierowujemy je do dowolnych adresów w pamięci jądra, umożliwiając operacje odczytu/zapisu.
|
||||||
|
|
||||||
#### 32-bitowy odczyt jądra
|
#### 32-Bit Kernel Read
|
||||||
|
|
||||||
Aby wykonać odczyt:
|
Aby wykonać odczyt:
|
||||||
|
|
||||||
1. Nadpisz **use count pointer**, aby wskazywał na docelowy adres pomniejszony o offset 0x14 bajtów.
|
1. Nadpisz **Use Count Pointer**, aby wskazywał na docelowy adres pomniejszony o offset 0x14 bajtów.
|
||||||
2. Użyj metody `get_use_count`, aby odczytać wartość spod tego adresu.
|
2. Użyj metody `get_use_count`, aby odczytać wartość pod tym adresem.
|
||||||
```c
|
```c
|
||||||
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
|
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
|
||||||
uint64_t args[1] = {surfaceID};
|
uint64_t args[1] = {surfaceID};
|
||||||
@ -188,7 +188,7 @@ return value;
|
|||||||
|
|
||||||
Aby wykonać zapis:
|
Aby wykonać zapis:
|
||||||
|
|
||||||
1. Nadpisz **indexed timestamp pointer** adresem docelowym.
|
1. Nadpisz **indexed timestamp pointer**, ustawiając go na docelowy adres.
|
||||||
2. Użyj metody `set_indexed_timestamp`, aby zapisać 64-bitową wartość.
|
2. Użyj metody `set_indexed_timestamp`, aby zapisać 64-bitową wartość.
|
||||||
```c
|
```c
|
||||||
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
|
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
|
||||||
@ -205,11 +205,11 @@ iosurface_set_indexed_timestamp_pointer(info.object, orig);
|
|||||||
```
|
```
|
||||||
#### Podsumowanie przebiegu exploita
|
#### Podsumowanie przebiegu exploita
|
||||||
|
|
||||||
1. **Trigger Physical Use-After-Free**: Strony zwolnione są dostępne do ponownego użycia.
|
1. **Trigger Physical Use-After-Free**: Wolne strony są dostępne do ponownego użycia.
|
||||||
2. **Spray IOSurface Objects**: Przydziel wiele obiektów IOSurface z unikalną "magic value" w pamięci jądra.
|
2. **Spray IOSurface Objects**: Alokuj wiele obiektów IOSurface z unikalną "magic value" w pamięci jądra.
|
||||||
3. **Identify Accessible IOSurface**: Zlokalizuj IOSurface na zwolnionej stronie, którą kontrolujesz.
|
3. **Identify Accessible IOSurface**: Zlokalizuj IOSurface na zwolnionej stronie, którą kontrolujesz.
|
||||||
4. **Abuse Use-After-Free**: Zmodyfikuj wskaźniki w obiekcie IOSurface, aby umożliwić dowolne **kernel read/write** poprzez metody IOSurface.
|
4. **Abuse Use-After-Free**: Zmodyfikuj wskaźniki w obiekcie IOSurface, aby umożliwić dowolne **kernel read/write** za pomocą metod IOSurface.
|
||||||
|
|
||||||
Dzięki tym prymitywom exploit zapewnia kontrolowane **32-bit reads** i **64-bit writes** do pamięci jądra. Kolejne kroki jailbreak mogą wymagać bardziej stabilnych prymitywów odczytu/zapisu, które mogą wymagać obejścia dodatkowych zabezpieczeń (np. PPL na nowszych urządzeniach arm64e).
|
Dzięki tym prymitywom exploit zapewnia kontrolowane **32-bit reads** i **64-bit writes** w pamięci jądra. Kolejne kroki jailbreak mogą obejmować bardziej stabilne prymitywy read/write, które mogą wymagać obejścia dodatkowych zabezpieczeń (np. PPL na nowszych urządzeniach arm64e).
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user