mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-23 05:24:46 -07:00
Dictionary attack functional
This commit is contained in:
@@ -15,6 +15,7 @@ enum {
|
|||||||
SubmenuIndexUnlockByReader,
|
SubmenuIndexUnlockByReader,
|
||||||
SubmenuIndexUnlockByPassword,
|
SubmenuIndexUnlockByPassword,
|
||||||
SubmenuIndexWrite,
|
SubmenuIndexWrite,
|
||||||
|
SubmenuIndexDictAttack
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -150,7 +151,8 @@ static NfcCommand
|
|||||||
}
|
}
|
||||||
if(!mf_ultralight_event->data->auth_context.skip_auth) {
|
if(!mf_ultralight_event->data->auth_context.skip_auth) {
|
||||||
mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password;
|
mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password;
|
||||||
mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key;
|
mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key;
|
||||||
|
// TODO: Key provided attribute is not set
|
||||||
}
|
}
|
||||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) {
|
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) {
|
||||||
instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack;
|
instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack;
|
||||||
@@ -201,6 +203,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
|
|||||||
SubmenuIndexUnlock,
|
SubmenuIndexUnlock,
|
||||||
nfc_protocol_support_common_submenu_callback,
|
nfc_protocol_support_common_submenu_callback,
|
||||||
instance);
|
instance);
|
||||||
|
if(data->type == MfUltralightTypeMfulC) {
|
||||||
|
submenu_add_item(
|
||||||
|
submenu,
|
||||||
|
"Unlock with Dictionary",
|
||||||
|
SubmenuIndexDictAttack,
|
||||||
|
nfc_protocol_support_common_submenu_callback,
|
||||||
|
instance);
|
||||||
|
}
|
||||||
} else if(
|
} else if(
|
||||||
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
|
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
|
||||||
data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 ||
|
data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 ||
|
||||||
@@ -269,6 +279,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
|
|||||||
} else if(event.event == SubmenuIndexCommonEdit) {
|
} else if(event.event == SubmenuIndexCommonEdit) {
|
||||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
} else if(event.event == SubmenuIndexDictAttack) {
|
||||||
|
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
instance->scene_manager, NfcSceneMfUltralightCDictAttack)) {
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
|
||||||
|
}
|
||||||
|
consumed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return consumed;
|
return consumed;
|
||||||
|
|||||||
@@ -181,10 +181,6 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po
|
|||||||
instance->general_event.protocol = NfcProtocolMfUltralight;
|
instance->general_event.protocol = NfcProtocolMfUltralight;
|
||||||
instance->general_event.event_data = &instance->mfu_event;
|
instance->general_event.event_data = &instance->mfu_event;
|
||||||
instance->general_event.instance = instance;
|
instance->general_event.instance = instance;
|
||||||
|
|
||||||
instance->dict_attack_ctx.auth_success = false;
|
|
||||||
instance->dict_attack_ctx.is_card_present = false;
|
|
||||||
|
|
||||||
mbedtls_des3_init(&instance->des_context);
|
mbedtls_des3_init(&instance->des_context);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -255,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller*
|
|||||||
instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
|
instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
|
||||||
instance->state = MfUltralightPollerStateGetFeatureSet;
|
instance->state = MfUltralightPollerStateGetFeatureSet;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Didn't response. Check Ultralight C");
|
FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C");
|
||||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||||
instance->state = MfUltralightPollerStateDetectMfulC;
|
instance->state = MfUltralightPollerStateDetectMfulC;
|
||||||
}
|
}
|
||||||
@@ -270,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo
|
|||||||
instance->data->type = MfUltralightTypeMfulC;
|
instance->data->type = MfUltralightTypeMfulC;
|
||||||
instance->state = MfUltralightPollerStateGetFeatureSet;
|
instance->state = MfUltralightPollerStateGetFeatureSet;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Didn't response. Check NTAG 203");
|
FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203");
|
||||||
instance->state = MfUltralightPollerStateDetectNtag203;
|
instance->state = MfUltralightPollerStateDetectNtag203;
|
||||||
}
|
}
|
||||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||||
@@ -456,7 +452,28 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
|
|||||||
command = instance->callback(instance->general_event, instance->context);
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
if(!instance->mfu_event.data->auth_context.skip_auth) {
|
if(!instance->mfu_event.data->auth_context.skip_auth) {
|
||||||
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
|
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
|
||||||
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
|
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
|
||||||
|
instance->auth_context.auth_success = false;
|
||||||
|
// For debugging
|
||||||
|
FURI_LOG_D(
|
||||||
|
"TAG",
|
||||||
|
"Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||||
|
instance->auth_context.tdes_key.data[0],
|
||||||
|
instance->auth_context.tdes_key.data[1],
|
||||||
|
instance->auth_context.tdes_key.data[2],
|
||||||
|
instance->auth_context.tdes_key.data[3],
|
||||||
|
instance->auth_context.tdes_key.data[4],
|
||||||
|
instance->auth_context.tdes_key.data[5],
|
||||||
|
instance->auth_context.tdes_key.data[6],
|
||||||
|
instance->auth_context.tdes_key.data[7],
|
||||||
|
instance->auth_context.tdes_key.data[8],
|
||||||
|
instance->auth_context.tdes_key.data[9],
|
||||||
|
instance->auth_context.tdes_key.data[10],
|
||||||
|
instance->auth_context.tdes_key.data[11],
|
||||||
|
instance->auth_context.tdes_key.data[12],
|
||||||
|
instance->auth_context.tdes_key.data[13],
|
||||||
|
instance->auth_context.tdes_key.data[14],
|
||||||
|
instance->auth_context.tdes_key.data[15]);
|
||||||
do {
|
do {
|
||||||
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
|
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
|
||||||
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
|
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
|
||||||
@@ -473,20 +490,41 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
|
|||||||
mf_ultralight_3des_shift_data(RndA);
|
mf_ultralight_3des_shift_data(RndA);
|
||||||
instance->auth_context.auth_success =
|
instance->auth_context.auth_success =
|
||||||
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
|
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
|
||||||
|
|
||||||
if(instance->auth_context.auth_success) {
|
if(instance->auth_context.auth_success) {
|
||||||
FURI_LOG_D(TAG, "Auth success");
|
FURI_LOG_E(TAG, "Auth success");
|
||||||
|
if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||||
|
memcpy(
|
||||||
|
&instance->data->page[44],
|
||||||
|
instance->auth_context.tdes_key.data,
|
||||||
|
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
|
||||||
|
instance->data->pages_read = instance->pages_total;
|
||||||
|
instance->pages_read = instance->pages_total;
|
||||||
|
instance->state = MfUltralightPollerStateReadSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
|
if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
|
||||||
FURI_LOG_D(TAG, "Auth failed");
|
FURI_LOG_E(TAG, "Auth failed");
|
||||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||||
|
if(instance->mode == MfUltralightPollerModeDictAttack) {
|
||||||
|
// Not needed? We already do a callback earlier?
|
||||||
|
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
|
||||||
|
command = instance->callback(instance->general_event, instance->context);
|
||||||
|
if(!instance->mfu_event.data->key_request_data.key_provided) {
|
||||||
|
instance->state = MfUltralightPollerStateReadPages;
|
||||||
|
} else {
|
||||||
|
instance->auth_context.tdes_key =
|
||||||
|
instance->mfu_event.data->key_request_data.key;
|
||||||
|
instance->state = MfUltralightPollerStateAuthMfulC;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance->state = MfUltralightPollerStateReadPages;
|
// Regression review
|
||||||
|
if(instance->mode != MfUltralightPollerModeDictAttack) {
|
||||||
|
instance->state = MfUltralightPollerStateReadPages;
|
||||||
|
}
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,12 +547,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
|
|||||||
instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
|
instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression review
|
||||||
|
const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4;
|
||||||
if(instance->error == MfUltralightErrorNone) {
|
if(instance->error == MfUltralightErrorNone) {
|
||||||
if(start_page < instance->pages_total) {
|
for(size_t i = 0; i < read_cnt; i++) {
|
||||||
FURI_LOG_D(TAG, "Read page %d success", start_page);
|
if(start_page + i < instance->pages_total) {
|
||||||
instance->data->page[start_page] = data.page[0];
|
FURI_LOG_D(TAG, "Read page %d success", start_page + i);
|
||||||
instance->pages_read++;
|
instance->data->page[start_page + i] = data.page[i];
|
||||||
instance->data->pages_read = instance->pages_read;
|
instance->pages_read++;
|
||||||
|
instance->data->pages_read = instance->pages_read;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(instance->pages_read == instance->pages_total) {
|
if(instance->pages_read == instance->pages_total) {
|
||||||
@@ -523,7 +565,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
|
|||||||
} else {
|
} else {
|
||||||
if(instance->data->type == MfUltralightTypeMfulC &&
|
if(instance->data->type == MfUltralightTypeMfulC &&
|
||||||
!mf_ultralight_3des_key_valid(instance->data)) {
|
!mf_ultralight_3des_key_valid(instance->data)) {
|
||||||
instance->state = MfUltralightPollerStateDictAttack;
|
instance->state = MfUltralightPollerStateCheckMfulCAuthStatus;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
|
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
|
||||||
if(instance->pages_read) {
|
if(instance->pages_read) {
|
||||||
@@ -732,63 +774,6 @@ static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller*
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NfcCommand mf_ultralight_poller_handler_dict_attack(MfUltralightPoller* instance) {
|
|
||||||
NfcCommand command = NfcCommandContinue;
|
|
||||||
const MfUltralightData* tag_data = instance->data;
|
|
||||||
uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type);
|
|
||||||
FURI_LOG_D(TAG, "Dict Attack");
|
|
||||||
do {
|
|
||||||
if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) {
|
|
||||||
instance->error = MfUltralightErrorProtocol;
|
|
||||||
instance->state = MfUltralightPollerStateReadFailed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance->dict_attack_ctx.is_card_present = true;
|
|
||||||
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
|
|
||||||
command = instance->callback(instance->general_event, instance->context);
|
|
||||||
if(!instance->mfu_event.data->key_request_data.key_provided) {
|
|
||||||
instance->state = MfUltralightPollerStateReadSuccess;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "Trying next 3DES key");
|
|
||||||
instance->error = MfUltralightErrorNone;
|
|
||||||
instance->auth_context.auth_success = false;
|
|
||||||
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
|
|
||||||
do {
|
|
||||||
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
|
|
||||||
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
|
|
||||||
furi_hal_random_fill_buf(RndA, sizeof(RndA));
|
|
||||||
instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output);
|
|
||||||
if(instance->error != MfUltralightErrorNone) break;
|
|
||||||
|
|
||||||
uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
|
|
||||||
const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
|
|
||||||
instance->error = mf_ultralight_poller_authenticate_end(
|
|
||||||
instance, RndB, output, decoded_shifted_RndA);
|
|
||||||
if(instance->error != MfUltralightErrorNone) break;
|
|
||||||
|
|
||||||
mf_ultralight_3des_shift_data(RndA);
|
|
||||||
instance->auth_context.auth_success =
|
|
||||||
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
|
|
||||||
if(instance->auth_context.auth_success) {
|
|
||||||
FURI_LOG_D(TAG, "Dict attack success");
|
|
||||||
instance->state = MfUltralightPollerStateReadPages;
|
|
||||||
instance->dict_attack_ctx.auth_success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
if(!instance->auth_context.auth_success) {
|
|
||||||
FURI_LOG_D(TAG, "Dict attack auth failed");
|
|
||||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
|
||||||
}
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const MfUltralightPollerReadHandler
|
static const MfUltralightPollerReadHandler
|
||||||
mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = {
|
mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = {
|
||||||
[MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle,
|
[MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle,
|
||||||
@@ -814,8 +799,6 @@ static const MfUltralightPollerReadHandler
|
|||||||
[MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages,
|
[MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages,
|
||||||
[MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail,
|
[MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail,
|
||||||
[MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success,
|
[MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success,
|
||||||
[MfUltralightPollerStateDictAttack] = mf_ultralight_poller_handler_dict_attack,
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {
|
static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
|
|||||||
uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
|
uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
|
||||||
mf_ultralight_3des_decrypt(
|
mf_ultralight_3des_decrypt(
|
||||||
&instance->des_context,
|
&instance->des_context,
|
||||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
instance->auth_context.tdes_key.data,
|
||||||
iv,
|
iv,
|
||||||
encRndB,
|
encRndB,
|
||||||
sizeof(encRndB),
|
sizeof(encRndB),
|
||||||
@@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start(
|
|||||||
|
|
||||||
mf_ultralight_3des_encrypt(
|
mf_ultralight_3des_encrypt(
|
||||||
&instance->des_context,
|
&instance->des_context,
|
||||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
instance->auth_context.tdes_key.data,
|
||||||
encRndB,
|
encRndB,
|
||||||
output,
|
output,
|
||||||
MF_ULTRALIGHT_C_AUTH_DATA_SIZE,
|
MF_ULTRALIGHT_C_AUTH_DATA_SIZE,
|
||||||
@@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end(
|
|||||||
|
|
||||||
mf_ultralight_3des_decrypt(
|
mf_ultralight_3des_decrypt(
|
||||||
&instance->des_context,
|
&instance->des_context,
|
||||||
instance->mfu_event.data->auth_context.tdes_key.data,
|
instance->auth_context.tdes_key.data,
|
||||||
RndB,
|
RndB,
|
||||||
bit_buffer_get_data(instance->rx_buffer) + 1,
|
bit_buffer_get_data(instance->rx_buffer) + 1,
|
||||||
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE,
|
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE,
|
||||||
|
|||||||
@@ -68,17 +68,10 @@ typedef enum {
|
|||||||
MfUltralightPollerStateWritePages,
|
MfUltralightPollerStateWritePages,
|
||||||
MfUltralightPollerStateWriteFail,
|
MfUltralightPollerStateWriteFail,
|
||||||
MfUltralightPollerStateWriteSuccess,
|
MfUltralightPollerStateWriteSuccess,
|
||||||
MfUltralightPollerStateDictAttack,
|
|
||||||
|
|
||||||
MfUltralightPollerStateNum,
|
MfUltralightPollerStateNum,
|
||||||
} MfUltralightPollerState;
|
} MfUltralightPollerState;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t sectors_total;
|
|
||||||
bool auth_success;
|
|
||||||
bool is_card_present;
|
|
||||||
} MfUltralightPollerDictAttackContext;
|
|
||||||
|
|
||||||
struct MfUltralightPoller {
|
struct MfUltralightPoller {
|
||||||
Iso14443_3aPoller* iso14443_3a_poller;
|
Iso14443_3aPoller* iso14443_3a_poller;
|
||||||
MfUltralightPollerState state;
|
MfUltralightPollerState state;
|
||||||
@@ -96,7 +89,6 @@ struct MfUltralightPoller {
|
|||||||
uint8_t tearing_flag_total;
|
uint8_t tearing_flag_total;
|
||||||
uint16_t current_page;
|
uint16_t current_page;
|
||||||
MfUltralightError error;
|
MfUltralightError error;
|
||||||
MfUltralightPollerDictAttackContext dict_attack_ctx;
|
|
||||||
mbedtls_des3_context des_context;
|
mbedtls_des3_context des_context;
|
||||||
|
|
||||||
NfcGenericEvent general_event;
|
NfcGenericEvent general_event;
|
||||||
|
|||||||
Reference in New Issue
Block a user