
Большинство людей не задумывается о случайном наборе букв и цифр, выдаваемых им Департаментом транспортных средств.
Но я не такой.
В Интернете я всегда стремлюсь получить понятный и запоминающийся цифровой идентификатор. Многие годы мне удавалось подбирать идентификаторы в виде моего имени + фамилии в Instagram* (@jlaf) и осмысленных слов на других платформах (@explain, @discontinue). Поэтому когда ДТС в третий раз прислал мне письмо с напоминанием о необходимости обновления номера, у меня сработал тот же инстинкт: почему я даже не задумался о том, чтобы получить номер с красивым сочетанием символов?
В мире автомобильных номеров США существует иерархия по степени редкости:
Номера из одной цифры (возможно всего 10 вариантов)
Номера из повторяющейся цифры (возможно всего 10 вариантов)
Номера из одной буквы (возможно 26 вариантов)
Номера из комбинаций повторяющихся букв (возможно ??? вариантов)
Номера из комбинаций двух букв (возможно 676 вариантов)
После изучения истории этих редких номеров моё любопытство достигло своего пика. Насколько редкий номер можно добыть? И насколько в ответе на этот вопрос мне могут помочь инструменты поиска, публично предоставляемые штатом?
PlateRadar и монополия
На текущий момент существует единственный ресурс для массового поиска информации о доступности автомобильных номеров: PlateRadar. Как и любой умный веб-сайт, PlateRadar понимает, что эти данные определённо имеют для кого-то большую важность, поэтому скрывает всю информацию, которую можно признать редкой, за ежемесячной подпиской стоимостью 20 долларов. Сайт обновляет информацию каждые 24 часа, а исходя из моего опыта добывания редких имён пользователя, я знаю, что время — самый важный фактор при ловле чего-то редкого. Обновления раз в сутки совершенно недостаточно.


К несчастью для PlateRadar, я не обычный человек, а разработчик, поэтому я решил изучить, как определяется доступность красивых номеров.
Сайт проверки красивых номеров Флориды
У Флориды, в отличие от некоторых других штатов (!), есть веб-сайт, позволяющий проверить конфигурацию автомобильного номера (то есть сочетание букв и цифр, которые будут напечатаны на номере) до того, как вы встанете в очередь в налоговом управлении. Веб-сайт также отображает типы номеров, поддерживающих указанную конфигурацию, потому что для разных номеров допускается разное ограничение на количество символов (например, в некоторых допустимо только пять символов, в других же может быть до семи).

К счастью, на сайте есть удобная фича, позволяющая проверять одновременно несколько комбинаций без дополнительных задержек в выполнении запроса. Я отправил несколько комбинаций вручную, а потом понял, что вручную это делается достаточно быстро, а значит, можно просто автоматизировать весь процесс.
Неограниченная частота
Я запустил Burp Suite и отправил через прокси запрос к сервису. Ответ был таким:
POST https://services.flhsmv.gov/mvcheckpersonalplate/ HTTP/1.1
__VIEWSTATE=/wEPDwULLTE2Nzg2NjE0NDgPZBYCZg9kFgICAw9kFgICAQ9kFgwCBQ8PFgIeBFRleHQFCUFWQUlMQUJMRWRkAgcPDxYCHgdWaXNpYmxlZ2RkAgsPDxYCHwAFASBkZAIRDw8WAh8ABQEgZGQCFw8PFgIfAAUBIGRkAh0PDxYCHwAFASBkZGQZj5Nowpt7uQW4i5K8gYM8k2+WSv9Zz0wpvFKj57zF0w==
__VIEWSTATEGENERATOR=0719FE0A
__EVENTVALIDATION=/wEdAAlM0TkirL0XIlY9Dw0k/5tSphigSR1TLsx/PgGne7pkToFkrQPgalhmo+FySJy6U4iQeyzYgJga2PpZFeMkYbpKuFA0Lbs4tsi+aCEe29qpNhTkiCU5GKYk9WuPyhuiSM5sZFBTNc+Q1lCok0SfYOt8+CHI2KGhrgOke/DbhB4LDccabLrTZbd0ckqhWOrhQ2MjwxuXnk/njUGbYQbYHdP4Ds+OFyUVKVe45DGbH/0quQ==
ctl00$MainContent$txtInputRowOne=MYPLATE
ctl00$MainContent$txtInputRowTwo
ctl00$MainContent$txtInputRowThree
ctl00$MainContent$txtInputRowFour
ctl00$MainContent$txtInputRowFive
ctl00$MainContent$btnSubmit=SubmitБлагодаря VIEWSTATE, VIEWSTATEGENERATOR и __EVENTVALIDATION я сразу же понял, что это веб-форма ASP.NET. Ну конечно, это ведь веб-сайт правительства штата, чего ещё можно было ожидать?
EVENTVALIDATION — это новая мера безопасности, реализованная в 2006 году командой ASP.NET для «предотвращения неавторизованных запросов, отправляемых потенциальными злоумышленниками из клиента; [..] чтобы гарантировать, что каждое событие postback и callback исходит от ожидаемых элементов пользовательского интерфейса, страница добавляет дополнительный слой валидации событий».
На практике, это должно препятствовать отправке поддельных форм, что теоретически не позволяет применять скрейпинг. Если бы мне пришлось получать новый набор этих переменных каждый раз, прежде чем выполнять какой-либо запрос, то я бы перегрузил систему и она почти сразу же бы ограничила частоту запросов.
... только у неё нет совершенно никакого ограничения по частоте.
У веб-сайта отсутствует CAPTCHA, ограничение частоты по IP и файрвол веб-приложения, которые могли бы предотвратить бесконечный поток запросов. Чтобы проверить это, я при помощи Burp Repeater выполнил множество запросов с нулевой полезной нагрузкой, и все они вернули код статуса 200 Successful.
Как только я это понял, то сразу написал короткий скрипт для автоматизации процесса. Он выглядит примерно так:
1. Один раз получаем страницу, используя реальные заголовки браузера; при этом загружается форма ASP.NET с VIEWSTATE, VIEWSTATEGENERATOR и __EVENTVALIDATION, что позволяет мне выполнить допустимый POST-запрос.
2. Извлекаем значения из формы при помощи Regex.
function extractFormFields(html: string): {
viewState: string;
viewStateGenerator: string;
eventValidation: string;
} {
const viewStateMatch = html.match(/id="__VIEWSTATE"\s+value="([^"]+)"/);
const viewStateGeneratorMatch = html.match(
/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/
);
const eventValidationMatch = html.match(
/id="__EVENTVALIDATION"\s+value="([^"]+)"/
);
if (!viewStateMatch || !viewStateGeneratorMatch || !eventValidationMatch) {
throw new Error("Failed to extract required form fields from page");
}
return {
viewState: viewStateMatch[1],
viewStateGenerator: viewStateGeneratorMatch[1],
eventValidation: eventValidationMatch[1],
};
}3. Собираем POST-запрос со всеми необходимыми полями. Сами комбинации номеров передаются через ctl00$MainContent$txtInputRowXXX, где XXX равно от one до five. Благодаря этому я мог проверять доступность номера в пять раз быстрее, а когда ищешь одновременно тысячи сочетаний номеров, это определённо важно.
function buildFormData(
plates: string[],
viewState: string,
viewStateGenerator: string,
eventValidation: string
): string {
const params = new URLSearchParams();
params.append("__VIEWSTATE", viewState);
params.append("__VIEWSTATEGENERATOR", viewStateGenerator);
params.append("__EVENTVALIDATION", eventValidation);
const fieldNames = [
"ctl00$MainContent$txtInputRowOne",
"ctl00$MainContent$txtInputRowTwo",
"ctl00$MainContent$txtInputRowThree",
"ctl00$MainContent$txtInputRowFour",
"ctl00$MainContent$txtInputRowFive",
];
for (let i = 0; i < 5; i++) {
params.append(
fieldNames[i],
i < plates.length ? plates[i].toUpperCase() : ""
);
}
params.append("ctl00$MainContent$btnSubmit", "Submit");
return params.toString();
}4. Отправляем POST-запрос и парсим тело! К счастью, сайт возвращает значение AVAILABLE или NOT AVAILABLE для каждой комбинации номера, поэтому это достаточно просто проверять в коде:
function extractPlateStatuses(
html: string,
plates: string[]
): PlateCheckResult[] {
const results: PlateCheckResult[] = [];
const labelIds = [
"MainContent_lblOutPutRowOne",
"MainContent_lblOutPutRowTwo",
"MainContent_lblOutputRowThree",
"MainContent_lblOutputRowFour",
"MainContent_lblOutputRowFive",
];
for (let i = 0; i < plates.length; i++) {
const labelId = labelIds[i];
const regex = new RegExp(`id="${labelId}"[^>]*>([^<]*)<`, "i");
const match = html.match(regex);
const status = match ? match[1].trim() : "";
const available = status.toUpperCase() === "AVAILABLE";
results.push({
plate: plates[i],
available,
status: status || "UNKNOWN",
});
}
return results;
}Битва за номера
Когда скрипт заработал, я создал маленький микросервис, добавлявший результаты в базу данных Postgres с сочетаниями номеров, а также с указанием последнего времени проверки. Доступность коротких, более ценных комбинаций (например, из одной буквы/двух букв) я постоянно запрашивал каждый час-два. В то время я не осознавал, что система обновляется в реальном времени. Когда кто-то резервировал номер, бэкенд ДТС Флориды сразу отражал это изменение в следующем поиске.
Для визуализации полученных скрейпингом данных я создал небольшой фронтенд на Next.js, позволявший мне просматривать результаты, фильтровать сочетания и пакетно загружать списки номеров из текстового файла для быстрой проверки.

Мне удалось найти несколько очень красивых сочетаний символов, например, WEBSITE, SITE и CAPTCHA. Но ничто не могло сравниться с нахождением единственной из оставшихся двухбуквенных комбинаций, которые мне встретились в процессе моего поиска: EO.
Я увидел, что EO свободен, 26 ноября. Мне подумалось, что из-за Дня благодарения, Чёрной пятницы и закрытых на все выходные правительственных учреждений можно не торопясь подъехать в налоговое управление и зарезервировать его.
Наступило 1 декабря, я запрыгнул в машину в 9:30 и поехал в управление. Пока я ехал, пришло уведомление от моего сервиса, что номер EO уже недоступен. У кого-то появилась та же мысль, что и у меня, и очевидно, он сразу подъехал к открытию офиса в 8:00. Потерпев поражение, я развернулся и поехал домой.
Вернувшись домой, от злости и любопытства я решил снова выполнить полную проверку всех двухбуквенных номеров.

Звёзды сошлись так, что снова оказалась свободной ещё одна двухбуквенная комбинация.
Я сразу перестал страдать, сел в машину и поехал прямиком в налоговое управление. Прождав почти час (и побеседовав с немного растерянным, но очень терпеливым чиновником, выслушавшим мои объяснения), мне наконец удалось зарезервировать комбинацию. HY официально стал моим автомобильным номером.

Я бы показал его фотографию, но, к сожалению, специально изготавливаемые номера Флорида выдаёт в течение 60 дней. Тем не менее, он существует и я его оплатил. Вот доказательство того, что благодаря TypeScript и огромной степени решимости возможно практически всё.
Meta Platforms*, а также принадлежащие ей социальные сети Facebook** и Instagram**:*признана экстремистской организацией, её деятельность в России запрещена;**запрещены в России