ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

亚马逊欺诈页面检测实战:从scam-amazon素材到Fullz特征工程

亚马逊欺诈页面检测实战:从scam-amazon素材到Fullz特征工程 简介这份资源围绕仿冒亚马逊页面的网络诈骗手法展开面向网络安全学习者、反欺诈研究人员及希望了解钓鱼攻击原理的技术人员用于剖析诈骗页面的构成与信息窃取流程。压缩包共24个文件以15个PHP脚本为核心辅以3个JavaScript、2个PNG、2个CSS及SVG等资源整体约167KB涵盖页面渲染、表单处理与数据收集等模块目录结构清晰便于按功能定位关键代码。目前已有135人学习下载。通过分析其登录页、邮件处理与检测拦截等模块读者可直观理解钓鱼页面如何诱导用户提交敏感信息并据此梳理识别可疑链接、核验站点真实性的防护思路对研究网络诈骗链路与提升安全意识具有参考价值。1. 从“scam-amazon_Fullz_page_amazonscam_Amazon_”这个标题能读出什么你如果在做电商风控、反欺诈或者安全研究大概率见过类似命名一串用下划线拼起来的英文看起来像某个仓库名、页面标识或者数据集标签。scam-amazon_Fullz_page_amazonscam_Amazon_这个标题拆开看就是几个信号叠加scam欺诈、amazon亚马逊、Fullz完整个人信息包、page页面、amazonscam亚马逊骗局。它指向的不是某个官方产品而是一类典型的“亚马逊欺诈页面/数据”研究素材——可能是钓鱼页面的 HTML 快照、欺诈话术模板、Fullz 数据样本或者一个用于训练检测模型的数据集标识。这类东西在反欺诈圈子里并不新鲜。真正值得花时间的是怎么把这种非结构化的、命名混乱的素材变成能跑通检测流程的输入。我见过太多团队拿到一堆页面快照或数据包直接扔给模型结果准确率玄学一样忽高忽低。问题不在模型在于你没搞清楚这些页面长什么样、Fullz 字段怎么分布、欺诈话术和正常商品页的差异在哪。这篇笔记就按我实际做过的路径从素材理解、字段拆解、特征提取到检测脚本一步步讲清楚。适合做电商风控、内容安全、反钓鱼的工程师也适合想复现一套可解释检测流程的安全研究者。2. 拆解 scam-amazon 页面素材从命名到字段的映射2.1 为什么不能直接把页面丢给模型scam-amazon_Fullz_page_amazonscam_Amazon_这种命名通常来自爬虫抓取时的自动拼接来源站点 页面类型 关键词 目标品牌。它本身不携带结构信息但命名里的每个片段都对应页面上的实际内容。我一般会先做一件事把素材按命名规则分组再抽样看每组页面的 DOM 结构和文本分布。常见做法是先统计页面里出现的表单字段。亚马逊欺诈页面通常伪装成登录、退款、账户验证或礼品卡兑换表单里会要求输入邮箱、密码、信用卡号、CVV、账单地址、社保号后四位等。Fullz 这个词在暗网语境里指一套完整的个人身份信息包括姓名、地址、电话、社保号、出生日期、信用卡数据。如果素材里包含 Fullz 样本那它大概率是结构化文本或 CSV而不是 HTML 页面。这两类素材的处理路径完全不同混在一起做特征工程会翻车。我一般会先跑一个快速分类脚本按文件扩展名和内容特征把素材分成三堆HTML 页面、纯文本话术、结构化数据CSV/JSON。这一步不需要复杂模型用正则和文件头就能搞定。import os import re def classify_asset(filepath): 按扩展名和内容特征粗分类素材 返回: html_page | text_script | structured_data | unknown ext os.path.splitext(filepath)[1].lower() if ext in [.html, .htm]: return html_page if ext in [.csv, .json, .xlsx]: return structured_data if ext in [.txt, .md]: # 进一步看内容是否包含表单关键词或 Fullz 字段 with open(filepath, r, errorsignore) as f: content f.read(2000).lower() fullz_keys [ssn, dob, cvv, card number, routing] if any(k in content for k in fullz_keys): return structured_data return text_script return unknown # 批量分类 asset_dir ./scam_amazon_samples groups {} for root, _, files in os.walk(asset_dir): for fn in files: fp os.path.join(root, fn) label classify_asset(fp) groups.setdefault(label, []).append(fp) for k, v in groups.items(): print(f{k}: {len(v)} files)这段脚本的逻辑很直白先看扩展名HTML 直接归页面CSV/JSON 归结构化数据TXT 文件再读前 2000 字符如果出现 SSN、CVV、卡号等关键词也归到结构化数据。参数上2000这个读取长度是我试出来的经验值——大部分 Fullz 样本在前 2000 字符内就会暴露字段名再长没必要。分类完你会得到三个桶后续分别处理。2.2 页面结构里真正有用的信号对于 HTML 页面别急着提取全文。亚马逊欺诈页面的核心信号集中在几个地方表单 action 指向的域名、输入框的 name/id 属性、页面标题和 meta 描述、以及是否有“验证账户”“立即退款”这类紧迫性话术。我一般会写一个提取器把每个页面压缩成一个固定长度的特征向量。from bs4 import BeautifulSoup import re def extract_page_features(html_content): 从单个 HTML 页面提取反欺诈特征 返回 dict字段固定方便后续转向量 soup BeautifulSoup(html_content, html.parser) features {} # 1. 表单 action 域名是否与亚马逊官方域名一致 forms soup.find_all(form) action_domains [] for f in forms: action f.get(action, ) # 提取域名部分 m re.search(rhttps?://([^/]), action) if m: action_domains.append(m.group(1)) features[form_count] len(forms) features[has_external_action] int(any( amazon.com not in d for d in action_domains )) # 2. 敏感输入字段数量 sensitive_names [password, pass, card, cvv, ssn, social, dob] input_fields soup.find_all([input, select]) sensitive_count 0 for inp in input_fields: name (inp.get(name) or ) (inp.get(id) or ) if any(s in name.lower() for s in sensitive_names): sensitive_count 1 features[sensitive_input_count] sensitive_count # 3. 紧迫性话术关键词命中数 text soup.get_text().lower() urgency_words [urgent, immediately, verify now, account suspended, refund pending, gift card, act now] features[urgency_hits] sum(1 for w in urgency_words if w in text) # 4. 页面标题是否包含品牌词但域名不匹配 title (soup.title.string or ).lower() if soup.title else features[title_has_amazon] int(amazon in title) return features # 示例处理一个页面 with open(./scam_amazon_samples/page_001.html, r, errorsignore) as f: html f.read() feat extract_page_features(html) print(feat)这里有几个参数需要按你的素材调整。sensitive_names列表可以根据实际页面扩充比如加上routing、account_number。urgency_words也是我一般会先跑一遍所有页面统计词频把出现频率高但正常页面也常见的词剔掉。has_external_action这个特征很关键——亚马逊官方页面的表单 action 基本都指向 amazon.com 域如果指向第三方域名欺诈概率极高。但要注意有些页面用 JavaScript 动态提交action 为空这种情况需要结合其他特征判断。2.3 Fullz 结构化数据的字段对齐如果素材里有 Fullz 样本通常是 CSV 或 JSON字段名可能五花八门ssn、social_security、ss_number都指同一个东西。我一般会先做一个字段名归一化映射表把常见变体映射到标准字段。标准字段常见变体full_namename, fullname, holder_namessnsocial_security, ss_number, socialdobbirth_date, date_of_birth, birthdaycard_numbercc_number, card_no, credit_cardcvvcvv2, cvc, security_codeaddressstreet, addr, billing_addressphonemobile, cell, telephone归一化之后再统计每个字段的缺失率和格式合规率。比如 SSN 应该是 9 位数字格式不对的要么是脏数据要么是故意混淆的。这一步用 pandas 很快。import pandas as pd import re # 字段映射 col_map { name: full_name, fullname: full_name, social_security: ssn, ss_number: ssn, birth_date: dob, date_of_birth: dob, cc_number: card_number, card_no: card_number, cvv2: cvv, cvc: cvv, street: address, addr: address, mobile: phone, cell: phone } df pd.read_csv(./scam_amazon_samples/fullz_sample.csv) df df.rename(columnslambda x: col_map.get(x.lower().strip(), x.lower().strip())) # 格式校验 def valid_ssn(v): return bool(re.fullmatch(r\d{3}-?\d{2}-?\d{4}, str(v))) def valid_card(v): return bool(re.fullmatch(r\d{13,19}, str(v).replace( , ).replace(-, ))) if ssn in df.columns: df[ssn_valid] df[ssn].apply(valid_ssn) if card_number in df.columns: df[card_valid] df[card_number].apply(valid_card) print(df[[ssn_valid, card_valid]].mean())这段代码先做列名归一化再对 SSN 和卡号做格式校验。valid_ssn允许带或不带连字符valid_card允许空格和连字符但要求 13 到 19 位数字。跑完你会得到每个字段的合规率如果某个字段合规率极低说明这批数据质量差或者字段映射错了需要回头检查。3. 用页面特征训练一个可解释的欺诈检测基线3.1 特征工程把页面变成向量上一章提取的页面特征还是 dict需要转成固定顺序的向量。我一般会定义一个特征顺序列表然后写一个转换函数。这样做的好处是训练和推理时特征顺序一致不会因为字典键顺序变化导致模型输入错位。FEATURE_ORDER [ form_count, has_external_action, sensitive_input_count, urgency_hits, title_has_amazon ] def features_to_vector(feat_dict): 按固定顺序转列表缺失填 0 return [feat_dict.get(k, 0) for k in FEATURE_ORDER] # 假设已经有一批页面特征 # page_features [extract_page_features(html) for html in all_htmls] # X [features_to_vector(f) for f in page_features]特征顺序一旦定下来就不要改否则旧模型无法复用。如果后续要加新特征追加到列表末尾并在训练时重新拟合。3.2 基线模型逻辑回归 特征重要性别一上来就上深度学习。对于这种特征维度低、样本量可能不大的场景逻辑回归足够用而且系数可以直接解释。我一般用 scikit-learn 跑一个基线看每个特征的权重和 AUC。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, roc_auc_score import numpy as np # 假设 X 是特征矩阵y 是标签1欺诈0正常 # X np.array([features_to_vector(f) for f in page_features]) # y np.array(labels) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) clf LogisticRegression(class_weightbalanced, max_iter1000) clf.fit(X_train, y_train) y_pred clf.predict(X_test) y_prob clf.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(fAUC: {roc_auc_score(y_test, y_prob):.4f}) # 特征重要性 for name, coef in zip(FEATURE_ORDER, clf.coef_[0]): print(f{name}: {coef:.4f})class_weightbalanced这个参数很重要因为欺诈样本通常远少于正常样本不加权的话模型会偏向多数类。max_iter1000是防止收敛慢导致警告。跑完看 AUC如果低于 0.85说明特征区分度不够需要回到上一章补充特征比如页面加载的外部资源域名、JavaScript 混淆程度、表单提交目标等。3.3 阈值怎么定别用默认的 0.5逻辑回归默认阈值 0.5但在反欺诈场景里漏判的代价通常高于误判。我一般会画 precision-recall 曲线根据业务能接受的误报率反推阈值。from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_test, y_prob) # 找 recall 0.9 时 precision 最高的阈值 target_recall 0.9 best_thresh 0.5 best_precision 0 for p, r, t in zip(precision, recall, thresholds): if r target_recall and p best_precision: best_precision p best_thresh t print(f阈值: {best_thresh:.4f}, 对应 precision: {best_precision:.4f})这个循环找的是在召回率不低于 0.9 的前提下精确率最高的阈值。实际部署时你可以根据每天的人工审核能力调整target_recall。如果审核人力有限就提高阈值降低误报如果漏判风险大就降低阈值。注意阈值不是定一次就完事。页面素材的分布会随时间变化建议每周重新跑一次 PR 曲线看阈值是否需要调整。4. 避坑与排查scam-amazon 素材处理中的五个血泪教训4.1 页面编码混乱导致特征提取失败现象跑extract_page_features时部分页面返回的form_count为 0但肉眼检查 HTML 里明明有表单。原因素材来自不同爬虫编码不统一有的用 UTF-8有的用 GBK 甚至 Latin-1。BeautifulSoup 如果没指定编码可能解析出乱码导致标签匹配失败。解决读取文件时先检测编码用chardet或直接尝试多种编码。我一般会写一个read_html_safe函数按 UTF-8、GBK、Latin-1 顺序尝试直到解析成功。def read_html_safe(filepath): for enc in [utf-8, gbk, latin-1]: try: with open(filepath, r, encodingenc) as f: return f.read() except UnicodeDecodeError: continue return 4.2 Fullz 字段映射错位导致统计失真现象归一化后ssn字段的合规率只有 20%但样本看起来是正常的。原因CSV 里列的顺序和表头不一致或者某些行有额外的逗号导致 pandas 解析错列。解决读 CSV 时加on_bad_lineswarn或error_bad_linesFalse旧版 pandas先看警告信息。然后用df.head()和df.dtypes检查每列内容是否符合预期。如果列错位严重考虑用csv.reader手动解析按逗号数量对齐。4.3 外部 action 特征误伤正常页面现象一些正常的亚马逊商品页也被判为欺诈因为has_external_action为 1。原因亚马逊有些页面确实会把表单提交到第三方支付网关或广告跟踪域名不全是欺诈。解决维护一个白名单域名列表比如pay.amazon.com、amazon-adsystem.com在计算has_external_action时排除这些域名。白名单需要根据实际素材逐步补充。4.4 紧迫性话术关键词在不同语言页面失效现象非英文的欺诈页面urgency_hits全是 0导致漏判。原因关键词列表只有英文。解决如果素材包含多语言页面需要为每种语言维护关键词列表或者用翻译 API 先转英文再匹配。我一般会先统计页面语言分布再决定是否值得做多语言。4.5 模型上线后特征漂移现象模型上线一个月后AUC 从 0.92 降到 0.78。原因欺诈页面改版了表单结构变化导致sensitive_input_count等特征分布偏移。解决监控每天的特征均值设置告警阈值。比如sensitive_input_count的日均值突然下降 30%就触发重新训练。另外定期用新素材做增量训练保持模型更新。5. 进阶用页面截图做视觉特征补充文本和 DOM 特征有个天然短板欺诈页面可以动态加载表单或者用图片代替文字这时候 DOM 里什么都抓不到。我一般会补一路视觉特征把页面截图用轻量 CNN 提取 embedding再和文本特征拼接。不需要从头训练用预训练的 MobileNet 提取 1280 维向量降维到 64 维后拼进逻辑回归。from PIL import Image import numpy as np from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input from tensorflow.keras.preprocessing.image import img_to_array # 加载预训练模型去掉分类头 base_model MobileNetV2(weightsimagenet, include_topFalse, poolingavg) def extract_visual_embedding(image_path): img Image.open(image_path).convert(RGB).resize((224, 224)) arr img_to_array(img) arr np.expand_dims(arr, axis0) arr preprocess_input(arr) embedding base_model.predict(arr, verbose0) return embedding.flatten() # 1280 维 # 降维拼接 from sklearn.decomposition import PCA # 假设 visual_embeddings 是 N x 1280 矩阵 pca PCA(n_components64, random_state42) visual_reduced pca.fit_transform(visual_embeddings) # 和文本特征拼接 # X_combined np.hstack([X_text, visual_reduced])MobileNetV2的poolingavg会输出 1280 维全局平均池化向量PCA降到 64 维是为了控制总特征维度避免过拟合。拼接后重新训练逻辑回归AUC 通常能提升 3 到 5 个百分点。但要注意截图需要额外存储和计算资源如果每天处理量很大得权衡成本。验证视觉特征是否有效我一般会做消融实验只用文本特征跑一次文本视觉跑一次对比 AUC 和召回率。如果提升不明显说明页面视觉差异不大不值得加这一路。最后说个习惯我每次拿到新的 scam-amazon 素材第一件事不是写模型而是随机抽 20 个页面和 20 条 Fullz 记录人工看一遍。看多了你会对欺诈页面的套路形成直觉知道哪些特征值得提取哪些是噪声。这个习惯帮我省了很多次返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表