Di Kala Menghadapi User Enumeration Attacks
Wed. Oct 1st, 2025 03:51 PM9 mins read
Di Kala Menghadapi User Enumeration Attacks
Source: Ideogram - user enumeration attacks

User Enumeration Attacks adalah teknik hacking dengan cara memilah akun mana yang benar-benar terdaftar dan mana yang tidak terdaftar. Dengan ditemukannya akun-akun yang terdaftar, maka akan memberikan kemudahan pada hacker untuk melakukan serangan lanjutan👨‍💻. Dia bisa dengan secara spesifik menentukan akun mana yang perlu dibobol lebih jauh. Misalnya akun orang kaya, banyak simpanan, orang terkenal, memiliki value tertentu, atau sejenisnya. Itu tentu lebih bernilai untuk dibobol daripada akun orang random yang ga sesuai target mereka. Mereka bisa skip akun dummy dan akun-akun yang menurut mereka ga worth it dibobol.

Specific Brute Force

Misalnya, John Wick terdaftar di sebuah aplikasi. Jika si hacker menemukan email atau nomor hp John Wick yang terdaftar di aplikasi itu valid, maka dia bisa fokus melanjutkan brute force login pada akun tersebut. Melakukan brute force pada list akun yang valid peluang berhasilnya lebih tinggi daripada selalu mencoba kombinasi user dan password secara random. Biasanya hacker punya list password yang umum digunakan dan beberapa user biasanya menggunakan password yang lemah😱.

Social Engineering

Bisa juga dengan memanfaatkan email atau nomor hp yang ditemukan di aplikasi tersebut sebagai target phishing. Si hacker akan mengirimkan John Wick email berpura-pura mengamankan akun dan mengirimkan link berbahaya. Atau menelepon nomor hp John Wick berpura-pura terjadi sesuatu lalu minta tebusan, hingga mengirimkan APK palsu. Hal kayak gini udah banyak korbannya dan masih sering terjadi🫣.

User Impersonate

Hacker juga bisa impersonate email John Wick kepada orang lain yang mengenal John Wick. Misalnya John Wick kenal Mad Dog. Si hacker bisa saja membuat email baru dimirip-miripin dengan email John Wick yang ia dapatkan dari aplikasi tadi untuk menipu Mad Dog. Misalkan email John Wick adalah johnny.wick01@mail.com. Si hacker akan membuat email dengan format mirip seperti johnny_wick01@mail.com. Sekilas ini mirip. Kalau Mad Dog tidak teliti dia akan dengan gampang percaya bahwa si pengirim adalah beneran John Wick. Si hacker bisa menipu Mad Dog atas nama John Wick untuk transfer uang atau sejenisnya.

Spam Target

Email dan nomor hp yang bocor bisa jadi target spam seperti untuk mengiklankan sesuatu. Misalkan John Wick suka sepatu, hacker bisa menjual data email dan nomor hp John Wick ke para penjual sepatu👟. Si penjual sepatu bisa spam email dan nomor hp John Wick untuk spam iklan sepatunya meskipun dia sendiri ga pernah subscribe.

Privacy Violation

Misalkan John Wick terdaftar di aplikasi forum anonim dengan username fafufafu. Orang-orang ga ada yang tahu itu siapa. Kalau si hacker berhasil mengetahui bahwa email dari user tersebut adalah johnny.wick01@mail.com, tentu dia bisa membocorkan bahwa identitas fafufafu itu adalah akun John Wick. Identitasnya di forum anonim itu jadi terkuak🤭.

Attacks on Login

Hacker bisa mendapatkan data email atau nomor hp user yang valid dengan memanfaatkan fitur dari aplikasi. Misalnya saat login hacker akan brute force berbagai kemungkinan kombinasi email atau username terkait John Wick kayak john.wick@mail.com, john.wick01@mail.com, johnny.wick, john_wick, dan sebagainya. Jika response dari halaman login tersebut user doesn't exist, maka si hacker bisa mengabaikannya dan mencoba kombinasi lain. Jika response dari halaman tersebut invalid password, maka si hacker berkesimpulan bahwa ini akunnya valid dan mereka dapat menentukan langkah selanjutnya.

Attacks on Registration

Saat pendaftaran, hacker juga bisa menggunakan kombinasi email dengan nama John Wick untuk mencari tahu email mana yang terdaftar di aplikasi tersebut. Jika responsenya berhasil, artinya email tersebut ga terdaftar. Sebaliknya, jika gagal berarti emailnya benar.

Attacks on Forgotten Password

Fitur ini sering dimanfaatkan hacker karena cukup simple. Saat membuka halaman ini biasanya user akan diminta memasukkan username, email, atau nomor hp. Jika ga valid maka server akan mengirimkan response user doesn't exist. Jika valid maka server akan mengirimkan OTP ke email atau nomor hp untuk verifikasi. Ada juga aplikasi yang menampilkan email atau nomor hp pemilik username tersebut pada response setelah submit seperti OTP has been sent to johnny.wick01@mail.com atau OTP has been sent to +62 811 1234 5678. Ini lebih bahaya lagi. Secara terang-terangan hacker dapat informasi sensitif🫣. Hal-hal kayak gini perlu dicegah dari sisi aplikasi untuk tidak sembarangan menampilkan informasi sensitif.

Preventable Action

Untuk mencegahnya ada beberapa langkah yang bisa dilakukan:

Generic Response

Kita harus memberikan response umum. Pada saat login ketika password, username, email, atau nomor hp salah, sebaiknya tetap berikan response yang sama seperti invalid password dan jangan kirim pesan user doesn't exist atau sejenisnya. Saat lupa password, sebaiknya email dan nomor hp disensor karakternya. Misalkan username John Wick yang asli adalah john.wick, maka yang muncul saat submit di halaman lupa password jadi kayak gini: OTP has been sent to j*****1@m*****.com atau OTP has been sent to ********5678. Begitu juga saat username tersebut tidak ditemukan maka tetap tampilkan pesan yang sama, tapi dengan email dummy dan ga beneran ngirim OTP ke email dummy tersebut. Misalkan username john_wick tidak terdaftar, maka di halaman lupa password response yang tampil kayak gini OTP has been sent to w*****i@g*****.com. Ketika hacker mencoba lagi hal yang sama dengan username lain yang juga tidak terdaftar, maka dia akan mendapatkan email dummy yang berbeda. Username seperti john-wick akan memberikan email dummy berbeda seperti j*****o@y*****.com. Pastikan juga status code-nya tetap 200 saat emailnya terdaftar maupun tidak. Status code 400 hanya diberikan untuk hal teknis misalnya kolom username tidak diisi atau sejenisnya. Saat submit OTP juga sama, apa pun masalahnya jika gagal responsenya harus sama kayak invalid OTP.

Consistent Message

Pastikan tiap email dummy konsisten dengan tiap username yang di-input agar tidak mencurigakan. Jadi ketika hacker mencoba lagi username palsu yang sama seperti sebelumnya dia akan mendapatkan email dummy yang sama. Username palsu john_wick tetap akan mendapatkan response email dummy w*****i@g*****.com persis seperti email dummy sebelumnya sesuai username palsu yang sama. Termasuk jumlah karakter yang disensor. Misalnya jumlah karakter yang disensor * adalah 5. Ini berlaku untuk semua username baik yang terdaftar maupun tidak. Meskipun email yang terdaftar adalah johnny.wick01@mail.com, jumlah karakter yang disensor jangan sesuai jumlah karakter email tersebut, tapi tetap dengan 5 karakter seperti j*****1@m*****.com. Ini agar hacker susah menebak jumlah karakter yang disensor. Dengan begini hacker akan sulit bedain mana email dummy dan mana email beneran😎.

Similar Performance

Hacker tetap bisa curiga ketika performa saat megirimkan email dummy dengan email beneran berbeda. Misalkan kalau email dummy performanya 30ms, sedangkan kalau emailnya beneran performanya 300ms. Ini jomplang banget walaupun ga terlalu terasa karena dapat dideteksi lewat tools. Biasanya karena kalau emailnya terdaftar akan ada beberapa step tambahan seperti menjalankan beberapa aktivitas lainnya ke database, mengirimkan OTP beneran pada halaman lupa password, dan sejenisnya. Sedangkan kalau emailnya ga terdaftar, prosesnya sesederhana generate message dan kirim ke response sehingga prosesnya jauh lebih cepat. Ini juga perlu diperhatikan dan perlu dibandingkan performanya. Jika jomplang maka bisa dengan menambahkan time sleep selama sekian ms mengikuti rerata performa saat menggunakan email asli agar performanya ga mencurigakan💤. Terkadang performa “lambat” itu ada gunanya😅.

Captcha

Ini juga perlu dipertimbangkan. Dengan menambahkan Captcha, kita menambah layer kemanan untuk mengonfirmasi bahwa yang melakukan aktivitas ini bukan bot. Tapi, dari sisi UX ini menjengkelkan user😓. Jalan tengahnya, ini digunakan pada fitur yang ga rutin dipake user seperti saat pendaftaran atau lupa password. Atau pada halaman login Captcha hanya dimunculkan jika salah password. Kita bisa bikin algoritma captcha sendiri atau menggunakan ReCaptcha dari Google.

Monitoring Alert

Ini berfungsi untuk memantau hal-hal yang ga wajar. Seperti sering mengakses menu lupa password terus-terusan, melakukan pendaftaran berulang-ulang, atau percobaan gagal login berkali-kali. Kita bisa menambahkan Rate Limiter untuk banned sementara IP yang mencurigakan selama beberapa jam atau bahkan hari. Tambahkan juga Geo Limiter untuk memantau lokasi yang mencurigakan. Misalkan secara history user tersebut sebelumnya mengakses aplikasi kita dari Indonesia. Kemudian dalam waktu dekat ada percobaan akses dari Trinidad & Tobago. Ini tentu mencurigakan, mana mungkin dalam hitungan menit user pindah benua🤔. Kita perlu blok akses tersebut dan kirim kode verifikasi ke kontak user untuk konfirmasi. Atau bisa juga melakukan pembatasan kesalahan saat login. Misalkan maksimal 3x salah masukin password. Jika lebih dari itu maka perlu kunci percobaan login sementara waktu dan mewajibkan user konfirmasi lewat email atau nomor hp🔒.

Sample Code

Untuk login flownya cukup jelas, tinggal handle kalau username, email, atau nomor hp ga terdaftar disamain aja pesan errornya kayak salah password. Kalau untuk pendaftaran menurut gw secara bisnis memang dibutuhkan informasi apakah emailnya sudah terdaftar atau belum. Lebih baik itu dihandle dengan menambahkan Captcha dan Rate Limiter agar fitur pendaftaran ga di-abuse. Sedangkan untuk lupa password kita perlu sensor informasi sensitif secara konsisten kayak gini:

User Class
Copy
@Value
public class User{
	String username;
	String password;
	String email;
	String phone;
}
UserGateway Interface
Copy
public interface UserGateway{
	User findByUsername(String username);
}
InMemoryGateway Interface
Copy
public interface InMemoryGateway{
	void store(String username, int otp, Duration duration);
}
NotificationClient Interface
Copy
public interface NotificationClient{
	void sendEmail(String email, String message);
	void sendSms(String phone, String message);
}
ForgottenPasswordPresenter Interface
Copy
public interface ForgottenPasswordPresenter{
	void present(ForgottenPasswordResponse response);
}
ForgottenPasswordResponse Class
Copy
@Value
public class ForgottenPasswordResponse{
	String message;
}
ForgottenPasswordUseCase Class
Copy
@RequiredArgsConstructor
public class ForgottenPasswordUseCase {
	private static final String ALPHABET = "qwertyuiopasdfghjklzxcvbnm";
	private static final List<String> EMAIL_DOMAIN = List.of("gmail","yahoo","mail");
	private static final SecureRandom GENERATOR = new SecureRandom();

	private final UserGateway userGateway;
	private final InMemoryGateway inMemoryGateway;
	private final NotificationClient notificationClient;

	public void execute(String usernameRaw, ForgottenPasswordPresenter presenter) {
		if(usernameRaw == null || usernameRaw.isBlank()){
			throw new IllegalArgumentException("username cannot be null or blank");
		}
		String username = usernameRaw.trim().toLowerCase();
		User user = userGateway.findByUsername(username);
		String contact = sendOtp(username, user);
		presenter.present(new ForgottenPasswordResponse("OTP has been sent to " + contact));
	}

	private String sendOtp(String username, User user){
		if(user != null){
			int otp = GENERATOR.nextInt(100_000, 1000_000);
			inMemoryGateway.store(username, otp, Duration.ofMinutes(5));
			if(user.getEmail() != null && !user.getEmail().isBlank()){
				String trimmed = user.getEmail().trim();
				notificationClient.sendEmail(trimmed, "this is your OTP: " + otp);
				return maskEmail(trimmed);
			} else if(user.getPhone() != null && !user.getPhone().isBlank()){
				String trimmed = user.getPhone().trim();
				notificationClient.sendSms(trimmed, "this is your OTP: " + otp);
				return maskPhone(trimmed);
			}
		}
		return maskFakeEmail(username);
	}

	private String maskFakeEmail(String username) {
		byte[] hash = hash(username);
		BigInteger hashedInt = new BigInteger(1, hash);

		BigInteger alphabetLength = BigInteger.valueOf(ALPHABET.length());
		int firstIndex = hashedInt.mod(alphabetLength).intValue();
		hashedInt = hashedInt.divide(alphabetLength);

		int lastIndex = hashedInt.mod(alphabetLength).intValue();
		hashedInt = hashedInt.divide(alphabetLength);

		int domainIndex = hashedInt.mod(BigInteger.valueOf(EMAIL_DOMAIN.size())).intValue();

		StringBuilder result = new StringBuilder();
		result.append(ALPHABET.charAt(firstIndex));
		for (int i = 0; i < 5; i++){
			result.append('*');
		}
		result.append(ALPHABET.charAt(lastIndex))
				.append('@')
				.append(EMAIL_DOMAIN.get(domainIndex).charAt(0));
		for (int i = 0; i < 5; i++){
			result.append('*');
		}
		return result.append(".com").toString();
	}

	@SneakyThrows
	private byte[] hash(String input) {
		TimeUnit.MILLISECONDS.sleep(GENERATOR.nextInt(150, 250));
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		return md.digest(input.getBytes(StandardCharsets.UTF_8));
	}

	private String maskEmail(String email){
		int atIndex = email.indexOf('@');
		if(atIndex < 0){
			return maskFakeEmail(email);
		}
		String localName = email.substring(0, atIndex);
		String domainName = email.substring(atIndex + 1);
		StringBuilder result = new StringBuilder();
		if(!localName.isEmpty()){
			result.append(localName.charAt(0));
		}
		for (int i = 0; i < 5; i++){
			result.append('*');
		}
		if(localName.length() > 1){
			result.append(localName.charAt(localName.length() - 1));
		}
		result.append('@');
		if(!domainName.isEmpty()){
			result.append(domainName.charAt(0));
		}
		for (int i = 0; i < 5; i++){
			result.append('*');
		}
		int dotPos = domainName.lastIndexOf(".");
		if(dotPos >= 0){
			result.append(domainName.substring(dotPos));
		}
		return result.toString();
	}

	private String maskPhone(String phone){
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < 8; i++){
			result.append('*');
		}
		if(phone.length() <= 4){
			for (int i = 0; i < 3; i++){
				result.append('*');
			}
			return result.append(phone.charAt(phone.length() - 1)).toString();
		}
		return result.append(phone.substring(phone.length() - 4)).toString();
	}

}

Flow dari code di atas kurang lebih seperti ini:

  • Kita akan mengecek username terlebih dahulu di database;
  • Jika ada maka kita akan generate OTP 6 digit. OTP tersebut kita store ke memory dan akan expire dalam 5 menit;
  • Jika user punya email, maka OTP dikirim ke email. Selain itu OTP akan dikirim ke nomor hp;
  • Jika menggunakan email, maka selain huruf pertama & huruf terakhir nama email serta huruf pertama domain email harus disensor untuk ditampilkan pada front end;
  • Jumlah karakter yang disensor adalah 5 walaupun jumlah karakter aslinya bukan 5;
  • Jika menggunakan nomor hp maka 8 digit awal akan disensor dan hanya 4 digit belakang aja yang ga disensor untuk ditampilkan pada front end;
  • Jika username ga terdaftar atau user ga punya email dan nomor hp maka kita akan menampilkan email dummy untuk ditampilkan pada front end dengan cara generate hash dari lowercase username menggunakan SHA-256 agar outputnya konsisten tiap username;
  • Kita manfaatkan kalkulasi modulo dari hash yang dikonversi ke BigInteger untuk mendapatkan indeks dari ALPHABET dan EMAIL_DOMAIN yang digunakan sebagai karakter dummy yang konsisten tiap username untuk karakter pertama dan karakter terakhir email, juga karakter pertama domain;
  • Saat hash kita tambahkan juga time sleep secara random kisaran 150ms hingga 250ms agar tidak ada perbedaan performa yang jomplang antara generate email dummy dengan yang bukan. Ini dengan asumsi performa aslinya kisaran 200ms hingga 300ms dan generate email dummy di kisaran 30ms hingga 50ms. Untuk hal ini perlu benchmark masing-masing aplikasi untuk menentukan nilainya;

Verdict

User Enumeration Attacks dapat membocorkan data sensitif user. Email pribadi dan nomor hp pribadi adalah hal sensitif user yang wajib kita jaga. Dengan serangan ini hacker bisa melakukan penipuan yang dapat merugikan user. Penipuan lewat social engineering, impersonate, hingga phishing masih banyak kejadian dan banyak pula yang jadi korbannya. Dengan melakukan pencegahan terhadap serangan ini kita sebagai pemilik aplikasi bisa melindungi data user. Di lain sisi ini tentu juga ada kekurangannya. Misalnya saat user aslinya lupa email dan password. Dengan hint email asli yang disensor bisa saja dia tetap ga ingat emailnya. Untuk hal begini biasanya akan ditangani oleh customer service. Ini tugas mereka untuk verifikasi apakah ini pemilik akun atau bukan.

© 2025 · Ferry Suhandri