使用TypeScript获取最稀有的车牌之一(2025年)
摘要
一名工程师使用TypeScript自动化检查佛罗里达州DMV网站上的稀有车牌可用性,绕过付费服务。
<p><a href="https://lobste.rs/s/jykbra/using_typescript_obtain_one_rarest">评论</a></p>
查看缓存全文
缓存时间: 2026/08/26 09:38
# 使用TypeScript获取最稀有的车牌之一
来源:https://www.jack.bio/blog/licenseplate
所有发布的观点和内容均代表个人立场,不代表任何现任或前任雇主。本站以个人身份维护。技术内容仅用于教育目的分享。
大多数人从不细想车管所分配给他们的随机字母数字组合。我却不是这样的人。在线上,我一直追求拥有简洁且令人印象深刻的数字身份。多年来,我成功获取了诸如在Instagram上与姓名完全一致的用户名(@jlaf),以及跨平台的完整单词账号(@explain, @discontinue)。
因此,当车管所寄来第三份车辆注册续期提醒邮件时,同样的本能开始发挥作用:为什么我没有考虑过为自己定制一组独特的车牌组合呢?
车牌世界存在着稀有度层级:
- 单数字车牌(10种可能)
- 重复数字车牌(10种可能)
- 单字母车牌(26种可能)
- 重复字母组合(???种可能)
- 双字母车牌组合(676种可能)
在研究这些稀有车牌的历史后,我的好奇心被彻底激发。究竟能稀有到什么程度?又能在多大程度上利用州政府的公共查询工具来探索呢?
## PlateRadar 与垄断现状
目前,要批量查询车牌可用性只有一个资源:PlateRadar。和所有聪明的网站一样,PlateRadar深知这些数据对某些人肯定有价值——因此,它将任何可能被视为稀有的信息都隐藏在每月20美元的付费墙后。此外,该网站每24小时刷新一次数据,而从我获取稀有用户名的经验来看,抢占稀有时机至关重要。24小时显然不够。
Image.png
Image.png
不幸的是,PlateRadar遇到了我这个工程师而非普通人,我决定深入研究个性化车牌的可用性判定机制。
## 佛罗里达州的个性化车牌检测器
与某些州不同(!),佛罗里达州提供了一个网站,允许你在浪费时间排队去税务办公室之前,先检查车牌配置(即你想印在车牌上的自定义字母/数字序列)。该工具还显示支持该组合的车牌类型,因为不同车牌允许的字符长度不同(例如,有些只允许5个字符,而有些允许最多7个字符)。
Image.png
值得庆幸的是,该网站有一个巧妙的功能,可以一次检查多个组合,且请求延迟无增加。我之前手动提交了一些组合,然后意识到手动请求的速度已经相当快——那么,如果我把整个流程自动化会怎样?
### 无限速率
我启动了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 Web Form。当然,这是政府网站,所以老实说,我还能期待什么呢?
EVENTVALIDATION(现在还是吗?)是ASP.NET团队在2006年实施的一项新奇安全措施,旨在“防止潜在恶意用户从客户端发送未经授权的请求[...],为了确保每一个回发和回调事件都源自预期的用户界面元素,页面在事件上添加了额外的验证层”。
实际上,它旨在阻止伪造的表单提交,理论上这应该是抓取的杀手锏。如果我必须在发起任何请求之前先获取一组新的这些变量,我很快就会因来回请求而压垮系统,并几乎立即被速率限制。
……但事实是,根本没有速率限制。完全没有。
看,该网站完全没有验证码、IP速率限制或网络应用防火墙来阻止大量请求涌入。我迅速使用Burp Repeater验证了这一点,发送了多个空载荷请求,所有请求都返回了200成功状态码。
一旦我意识到这一点,我迅速编写了一个脚本来自动化整个流程。工作流程大致如下:
1. 使用真实的浏览器标头获取页面一次,这将加载ASP.NET表单并提供`__VIEWSTATE`、`__VIEWSTATEGENERATOR`和`__EVENTVALIDATION`——以及发起合法POST请求的权限。
2. 使用正则表达式辅助函数从表单中提取值。
```typescript
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`。使用这个方法让我检查车牌可用性的速度提高了5倍——当一次检查数千个车牌组合时,这绝对很重要。
```typescript
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`(不可用),因此用代码检查起来很容易:
```typescript
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;
}
```
## 2025年的车牌大战
脚本顺利运行后,我创建了一个小型微服务,将结果(包括车牌组合及最后检查时间)添加到Postgres数据库中。对于较小的、高价值的组合(例如,任何单字母/双字母组合),我每隔一两个小时就轮询一次以检查可用性。
当时我*没有*意识到的是,该系统是实时更新的。有人预订车牌的那一刻,佛罗里达州车管所的后端在下一次查询时就反映了这一变化。
为了可视化我抓取的数据,我快速构建了一个Next.js前端,让我可以浏览结果、过滤组合,并从文本文件批量上传车牌列表以进行快速检查。
Image.png
我发现了一些非常酷的车牌组合,比如`WEBSITE`、`SITE`和`CAPTCHA`。但没有什么能比得上我搜索期间发现的仅存的双字母组合之一:`EO`。
我在11月26日看到`EO`是可用的。考虑到感恩节、黑色星期五以及整个周末州政府办公室关闭,我以为我有充足的时间悠闲地走进税务办公室拿下它。
12月1日,我早上9:30跳上车前往税务办公室。开车途中,我收到我的服务发来的通知,显示`EO`不再可用。有人和我有同样的想法,并且显然在办公室早上8点开门时就准时到了。我调转车头,沮丧地回家了。
到家后,出于赌气(和好奇),我决定重新运行对所有双字母车牌的全面检查。
Image.png
就这样,通过某种奇怪的、神圣的时机巧合,另一个双字母组合重新变为可用。我的郁闷很快结束,我立刻跳回车里,径直驶向办公室。
经过近一小时的等待(以及与一位有点困惑但非常耐心的办公室职员解释我的情况),我成功完成了预订。`HY`正式成为我的车牌。
Image.png
我本想给你看张照片,但不幸的是,佛罗里达州的定制车牌需要60天才能送达。不过:它存在,它已支付,它证明了只要有一点TypeScript和超乎常理的决心,你几乎可以声称拥有任何东西。
相似文章
新监控技术将你的手机与车牌关联起来
Ars Technica 报道了 Leonardo 的监控系统 SignalTrace,该系统将蓝牙/设备信号与车牌关联,帮助警方在事先不知晓身份的情况下识别相关人员,引发隐私担忧。
我租了一辆车,几小时内我的驾照就被出售了
一个暗网网站正在出售1.53亿张驾照,可能是从租车公司使用的身份证扫描服务中实时获取的,导致FBI介入调查。
在加州的一个小镇,Flock 向警方发送的警报中有 71% 误读了车牌
商业内幕网的一项调查发现,加州罗斯维尔的 Flock Safety 人工智能车牌阅读器在发送给警方的警报中,有 71% 误读了车牌,引发了对该技术可靠性的担忧。
从 localStorage 检索对象的类型定义
本文介绍了一种 TypeScript 解决方案,通过在自定义 Db 类中使用映射函数,在从 localStorage 检索对象时保留对象方法。
TypeScript 如何分配联合类型
一篇深度文章,解释 TypeScript 如何在重载函数、方法接收者和条件类型中分配联合类型,并包含示例和解决方法。