使用TypeScript获取最稀有的车牌之一(2025年)

Lobsters Hottest 新闻

摘要

一名工程师使用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

Ars Technica 报道了 Leonardo 的监控系统 SignalTrace,该系统将蓝牙/设备信号与车牌关联,帮助警方在事先不知晓身份的情况下识别相关人员,引发隐私担忧。

TypeScript 如何分配联合类型

Lobsters Hottest

一篇深度文章,解释 TypeScript 如何在重载函数、方法接收者和条件类型中分配联合类型,并包含示例和解决方法。