跳到正文
选择国家与语言当前国家: 中国

德勤 Virtual Internship / 任务 03

实习猿原创实训 · 任务 03

导出接口访问调查

以策略、会话和请求证据识别控制缺口,给出可检验的处置建议。

先查看已完成的成果,再使用材料完成自己的交付。

先阅读完整成果

材料

learner/materials/access-log.csv 阅读完整文件下载文件
request_idatusersessionrolemethodpathstatusrowsbytesip
Q00012026-09-10T08:00:00Zop-1s-0operatorPOST/login20000192.0.2.10
Q00022026-09-10T08:01:00Zop-1s-0operatorGET/orders?hub=海港200204000192.0.2.10
Q00032026-09-10T08:02:00Zop-2s-1operatorPOST/login20000192.0.2.10
Q00042026-09-10T08:03:00Zop-2s-1operatorGET/orders?hub=东城200204000192.0.2.10
Q00052026-09-10T08:04:00Zop-3s-2operatorPOST/login20000192.0.2.10
Q00062026-09-10T08:05:00Zop-3s-2operatorGET/orders?hub=西岭200204000192.0.2.10
Q00072026-09-10T08:06:00Zop-4s-3operatorPOST/login20000192.0.2.10
Q00082026-09-10T08:07:00Zop-4s-3operatorGET/orders?hub=南湾200204000192.0.2.10
Q00092026-09-10T08:08:00Zop-1s-4operatorPOST/login20000192.0.2.10
Q00102026-09-10T08:09:00Zop-1s-4operatorGET/orders?hub=海港200204000192.0.2.10
Q00112026-09-10T08:10:00Zop-2s-5operatorPOST/login20000192.0.2.10
Q00122026-09-10T08:11:00Zop-2s-5operatorGET/orders?hub=东城200204000192.0.2.10
Q00132026-09-10T08:12:00Zop-3s-6operatorPOST/login20000192.0.2.10
Q00142026-09-10T08:13:00Zop-3s-6operatorGET/orders?hub=西岭200204000192.0.2.10
Q00152026-09-10T08:14:00Zop-4s-7operatorPOST/login20000192.0.2.10
Q00162026-09-10T08:15:00Zop-4s-7operatorGET/orders?hub=南湾200204000192.0.2.10
Q00172026-09-10T08:16:00Zop-1s-8operatorPOST/login20000192.0.2.10
Q00182026-09-10T08:17:00Zop-1s-8operatorGET/orders?hub=海港200204000192.0.2.10
Q00192026-09-10T08:18:00Zop-2s-9operatorPOST/login20000192.0.2.10
Q00202026-09-10T08:19:00Zop-2s-9operatorGET/orders?hub=东城200204000192.0.2.10
Q00212026-09-10T08:20:00Zop-3s-10operatorPOST/login20000192.0.2.10
Q00222026-09-10T08:21:00Zop-3s-10operatorGET/orders?hub=西岭200204000192.0.2.10
Q00232026-09-10T08:22:00Zop-4s-11operatorPOST/login20000192.0.2.10
Q00242026-09-10T08:23:00Zop-4s-11operatorGET/orders?hub=南湾200204000192.0.2.10
Q00252026-09-10T08:24:00Zop-1s-12operatorPOST/login20000192.0.2.10
Q00262026-09-10T08:25:00Zop-1s-12operatorGET/orders?hub=海港200204000192.0.2.10
Q00272026-09-10T08:26:00Zop-2s-13operatorPOST/login20000192.0.2.10
Q00282026-09-10T08:27:00Zop-2s-13operatorGET/orders?hub=东城200204000192.0.2.10
Q00292026-09-10T08:28:00Zop-3s-14operatorPOST/login20000192.0.2.10
Q00302026-09-10T08:29:00Zop-3s-14operatorGET/orders?hub=西岭200204000192.0.2.10
Q00312026-09-10T08:30:00Zop-4s-15operatorPOST/login20000192.0.2.10
Q00322026-09-10T08:31:00Zop-4s-15operatorGET/orders?hub=南湾200204000192.0.2.10
Q00332026-09-10T08:32:00Zop-1s-16operatorPOST/login20000192.0.2.10
Q00342026-09-10T08:33:00Zop-1s-16operatorGET/orders?hub=海港200204000192.0.2.10
Q00352026-09-10T08:34:00Zop-2s-17operatorPOST/login20000192.0.2.10
Q00362026-09-10T08:35:00Zop-2s-17operatorGET/orders?hub=东城200204000192.0.2.10
Q00372026-09-10T08:36:00Zop-3s-18operatorPOST/login20000192.0.2.10
Q00382026-09-10T08:37:00Zop-3s-18operatorGET/orders?hub=西岭200204000192.0.2.10
Q00392026-09-10T08:38:00Zop-4s-19operatorPOST/login20000192.0.2.10
Q00402026-09-10T08:39:00Zop-4s-19operatorGET/orders?hub=南湾200204000192.0.2.10
Q00412026-09-10T08:40:00Zop-1s-20operatorPOST/login20000192.0.2.10
Q00422026-09-10T08:41:00Zop-1s-20operatorGET/orders?hub=海港200204000192.0.2.10
Q00432026-09-10T08:42:00Zop-2s-21operatorPOST/login20000192.0.2.10
Q00442026-09-10T08:43:00Zop-2s-21operatorGET/orders?hub=东城200204000192.0.2.10
Q00452026-09-10T08:44:00Zop-3s-22operatorPOST/login20000192.0.2.10
Q00462026-09-10T08:45:00Zop-3s-22operatorGET/orders?hub=西岭200204000192.0.2.10
Q00472026-09-10T08:46:00Zop-4s-23operatorPOST/login20000192.0.2.10
Q00482026-09-10T08:47:00Zop-4s-23operatorGET/orders?hub=南湾200204000192.0.2.10
Q00492026-09-10T09:20:00Zsvc-archivesvc-1servicePOST/login20000192.0.2.10
Q00502026-09-10T09:21:00Zsvc-archivesvc-1serviceGET/export?hub=ALL20010000800000192.0.2.10
Q00512026-09-10T09:22:00Zsvc-archivesvc-1serviceGET/export?hub=ALL20010000800000192.0.2.10
Q00522026-09-10T09:23:00Zsvc-archivesvc-1serviceGET/export?hub=ALL20010000800000192.0.2.10
Q00532026-09-10T09:24:00Zsvc-archivesvc-1serviceGET/export?hub=ALL20010000800000192.0.2.10
Q00542026-09-10T09:25:00Zsvc-archivesvc-1serviceGET/export?hub=ALL20010000800000192.0.2.10
Q00552026-09-10T09:30:00Zlead-1lead-1supervisorPOST/login20000192.0.2.10
Q00562026-09-10T09:31:00Zlead-1lead-1supervisorGET/export?hub=海港200100080000192.0.2.10
Q00572026-09-10T09:40:00Zop-7sus-7operatorPOST/login20000198.51.100.7
Q00582026-09-10T09:41:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00592026-09-10T09:42:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00602026-09-10T09:43:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00612026-09-10T09:44:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00622026-09-10T09:45:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00632026-09-10T09:46:00Zop-7sus-7operatorGET/export?hub=ALL200120096000198.51.100.7
Q00642026-09-10T10:00:00ZunknownnoneanonymousGET/export?hub=ALL40100203.0.113.9
Q00652026-09-10T10:01:00ZunknownnoneanonymousGET/export?hub=ALL40100203.0.113.9
Q00662026-09-10T10:02:00ZunknownnoneanonymousGET/export?hub=ALL40100203.0.113.9
Q00672026-09-10T10:03:00ZunknownnoneanonymousGET/export?hub=ALL40100203.0.113.9
Q00682026-09-10T10:30:00Zop-2expired-2operatorPOST/login20000192.0.2.10
Q00692026-09-10T11:15:00Zop-2expired-2operatorGET/orders?hub=东城2005010000192.0.2.10
learner/materials/access-policy.json 阅读完整文件下载文件
{
  "session_ttl_minutes": 30,
  "export_roles": [
    "supervisor",
    "service"
  ],
  "operator_hub": {
    "op-1": "海港",
    "op-2": "东城",
    "op-3": "西岭",
    "op-4": "南湾",
    "op-7": "海港"
  },
  "network_note": "经企业 VPN 访问网关;IP 为文档保留地址,不能证明真实地理位置或攻击者身份。",
  "service_accounts": [
    "svc-archive"
  ]
}
learner/materials/data-dictionary.md 阅读完整文件下载文件

数据字典与口径

业务工作单

你加入冷链履约运营项目组。运营负责人需要回答:哪些订单与站点需要先处理、导出接口哪里存在控制缺口,以及下一版工作台应交付什么。四个站点为海港、东城、西岭、南湾。standard 承诺 24 h,express 承诺 8 h;具体截止时间以 orders.csv 的 due_at 为准。

材料覆盖 2026 年 9 月的订单及事件,统一复盘截止点为 rules.json 的 cutoff。所有业务时间转 UTC;本地显示时可以换时区,但计算不能把无时区字符串当成本地时间。输入可包含截止点之后的事件,用于检查是否错误使用未来信息。

orders.csv

  • order_id:唯一订单号,字符串,不转成数字。
  • hub:站点名;service:standard/express;route:线路编号。
  • created_at:带时区的下单时刻;due_at:承诺交付时刻,UTC ISO 8601。
  • 一行代表一个订单,事件不等于订单;不要按事件行数统计订单。

两个事件源

  • A:event_id → event_id;order_ref → order_id;hub → hub;service → service;event → kind;event_time → happened_at。
  • B:id → event_id;shipment.number → order_id;shipment.station → hub;shipment.product → service;action 的 OPEN/COLLECT/DROP/VOID 映射 created/picked_up/delivered/cancelled;at_ms 为整数 Unix 毫秒,输出为 UTC ISO 时间。
  • 标准事件必须且只需包含 event_id、order_id、hub、service、kind、happened_at。来源文件与行号写入异常/重复清单。
  • 同 event_id 标准内容完全相同:只留一条,其他计 exact_replay。同 event_id 内容不同:整组隔离,不能先到先得。坏字段、未知动作或无时区时间:隔离并保留来源及原因。
  • 禁止原地修改原始材料。对账:原始事件数 = 标准事件数 + 隔离记录数 + 重复剔除数。

截止日履约口径

按订单聚合,先只保留 happened_at ≤ cutoff 的事件。取消单优先排除;due_at > cutoff 的非取消单记 not_mature。其他订单:截止前无交付记 overdue_open;首次交付时刻 > due_at 记 delivered_late;其余 on_time。恰好到期算成熟,恰好交付算准时。

成熟分母 = on_time + delivered_late + overdue_open。迟交分子 = delivered_late + overdue_open。取消与未到期均不得填为准时。已交付迟交分钟按交付时间减 due_at,未交付逾期分钟按 cutoff 减 due_at,两者单列。此材料不包含退款、食物损坏或实际成本,不能推出这些金额。

服务审查口径

rules.json 固定目标迟交率为 10%,最小成熟样本 15。差距用百分点,不用百分比变化。成熟样本 <15 优先归“待补样本”;差距 >15 个百分点归“优先处理”;>5 且 ≤15 归“需要复核”;≤5 归“达到阈值”。这些是本次运营审查规则,不是统计显著性或公平性的证明。

统一服务结构比较:standard 和 express 各 50% 权重。两个服务层均满足最小样本才计算站点标准化迟交率;否则保留空值并说明缺口。不得用无依据的总体平均填补小样本。业务假设须与观察事实分开。

访问日志与授权策略

access-log.csv 一行一个请求。request_id 唯一;at 为 UTC;user、session、role 为请求上下文;method、path 为路由;status 为响应码;rows、bytes 是网关响应计数。它们不证明唯一客户数,也不证明接收者已使用数据。

access-policy.json 是本次调查的授权基线。operator 只能读取被授权站点,不能 export。supervisor 与 service 允许导出。登录成功后 session 有效 30 分钟,恰好 30 分钟有效;之后应拒绝。401 请求代表被拒绝的尝试,不是成功泄露。svc-archive 是批准的归档账号,频繁大批导出不能只因次数高就定性。日志没有文件正文、终端取证或 VPN 会话记录,不能据此归属自然人、地理位置或入口路径。

步骤

  1. 先把策略转成三条检查:导出角色权限、站点范围、session 有效期。列出判断所需字段,不用 IP 地理猜测身份。
  1. 按 user+session 匹配成功登录与后续请求,计算经过分钟。30 分钟边界有效,超时后 200 响应需调查。登录失败不能续期。
  1. 只对成功响应统计可能返回的 rows 与 bytes。对 401 单列尝试,不把它加到成功导出。
  1. 列出 request_id、时间、账号、策略差异和响应量,区分一个请求触犯多个规则与多个事件,避免重复计量。
  1. 检查 svc-archive 与 lead-1 的高量导出,说明为什么规则允许它们。给出一个仅按频次检测会误报的反证。
  1. 写 incident-report.md:时间线、影响范围、已知/未知、P0 处置建议与负责人、验证及回退。任何禁用账号、撤销 session 都是建议,实训不触碰实际账号。

完整成果

访问控制事件调查报告

发现与严重程度

优先级 P0:operator 账号 op-7 在有效 session sus-7 下成功导出 ALL 站点,违反导出角色与站点范围两条规则。09:41–09:46 UTC 共 6 条成功响应,证据 Q0058–Q0063。网关记录返回量累计 7,200 行、576,000 字节。该数量不是唯一客户数,日志不证明文件被保存、传播或被哪位自然人操作。

另一个独立缺口:op-2 的 expired-2 在 10:30 登录,11:15 仍收到 200,间隔 45 分钟,超过 30 分钟有效期,证据 Q0068 与 Q0069。它返回 50 行、10,000 字节,单独记录,不加到 op-7 导出量。

时间线与反证

处置建议

P0:安全值班人员先保全网关日志、导出审计与身份服务登录记录,记录哈希和访问权限。授权运维人员临时关闭 operator 导出路由,并对 sus-7 撤销会话;本次实训没有执行这些实际操作。产品负责人检查是否有其他受影响导出批次;合规负责人结合真实文件内容判断后续义务,当前日志不足以判断。

P1:API 在每次请求上检查角色和站点范围;对 session 强制执行 TTL;service 单独配置最小导出范围并记录批次标识。告警按成功越权而不是单纯频率设计。

验证与回退

修复验收:operator /export 返回 403,operator 读取他站点返回 403;有效 supervisor 与 svc-archive 返回 200;session 恰好 30 分钟允许、超过 30 分钟返回 401。回归 /orders 的合法访问。若误阻断业务,保留 operator 导出禁用,通过已授权 supervisor 完成必要受控导出,不取消访问检查。

未知:入口是否经 VPN、凭据是否被盗、自然人身份、文件是否离开终端。需要 VPN 会话与终端证据,不因 IP 字段就下结论。

下载文件

参考答案

instructor/answers/security-findings.csv 阅读完整文件下载文件
request_idatusersessionfindingstatusrowsbytes
Q00582026-09-10T09:41:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00592026-09-10T09:42:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00602026-09-10T09:43:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00612026-09-10T09:44:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00622026-09-10T09:45:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00632026-09-10T09:46:00Zop-7sus-7unauthorised_export;hub_scope_violation200120096000
Q00692026-09-10T11:15:00Zop-2expired-2expired_or_missing_session2005010000
完整参考实现 阅读完整文件下载文件
"""Reference implementation for the Ape Intern cold-chain operations lab."""
import csv,json
from collections import defaultdict,Counter
from datetime import datetime,timezone
from pathlib import Path
from urllib.parse import urlparse,parse_qs
UTC=timezone.utc
KINDS={'OPEN':'created','COLLECT':'picked_up','DROP':'delivered','VOID':'cancelled'}
HUBS={'海港','东城','西岭','南湾'}
def parse_time(value):
    t=datetime.fromisoformat(value.replace('Z','+00:00'))
    if t.tzinfo is None:raise ValueError('timezone_required')
    return t.astimezone(UTC)
def iso(t):return t.isoformat(timespec='seconds').replace('+00:00','Z')
def readcsv(p):
    with Path(p).open(encoding='utf-8-sig',newline='') as f:return list(csv.DictReader(f))
def writecsv(p,rows,fields=None):
    rows=list(rows)
    with Path(p).open('w',encoding='utf-8-sig',newline='') as f:
        w=csv.DictWriter(f,fieldnames=fields or list(rows[0]));w.writeheader();w.writerows(rows)
def dump(p,value):Path(p).write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n')
def _normalize(record,source):
    if source=='A':
        e=dict(event_id=record['event_id'],order_id=record['order_ref'],hub=record['hub'],service=record['service'],kind=record['event'],happened_at=iso(parse_time(record['event_time'])))
    else:
        ms=record['at_ms']
        if type(ms) is not int:raise ValueError('epoch_must_be_integer')
        e=dict(event_id=record['id'],order_id=record['shipment']['number'],hub=record['shipment']['station'],service=record['shipment']['product'],kind=KINDS[record['action']],happened_at=iso(datetime.fromtimestamp(ms/1000,UTC)))
    if e['kind'] not in KINDS.values():raise ValueError('unsupported_event')
    if e['hub'] not in HUBS or e['service'] not in {'standard','express'}:raise ValueError('unknown_dimension')
    if not e['event_id'] or not e['order_id']:raise ValueError('empty_key')
    return e

def normalize(record,source):
    try:return _normalize(record,source)
    except (KeyError,TypeError,OverflowError) as error:raise ValueError(str(error).strip("'")) from error

def ingest(data):
    grouped=defaultdict(list);rejected=[];duplicates=[]
    for source,name in [('A','dispatch-a.jsonl'),('B','courier-b.jsonl')]:
        for line,text in enumerate((data/name).read_text().splitlines(),1):
            loc={'source':name,'line':line}
            try:
                event=normalize(json.loads(text),source);grouped[event['event_id']].append((event,loc))
            except (ValueError,KeyError,TypeError,OverflowError) as ex:
                rejected.append(dict(loc,reason=str(ex).strip("'")))
    accepted=[]
    for eid,items in grouped.items():
        distinct={json.dumps(e,sort_keys=True,ensure_ascii=False) for e,loc in items}
        if len(distinct)>1:
            rejected.extend(dict(loc,reason='immutable_id_conflict') for e,loc in items);continue
        accepted.append(items[0][0])
        duplicates.extend(dict(loc,event_id=eid,reason='exact_replay') for e,loc in items[1:])
    accepted.sort(key=lambda e:(e['happened_at'],e['event_id']))
    return accepted,rejected,duplicates

def classify_order(order,events,cutoff):
    # Only facts available at cutoff can influence a retrospective result.
    visible=[e for e in events if parse_time(e['happened_at'])<=cutoff]
    visible.sort(key=lambda e:parse_time(e['happened_at']))
    delivered=[parse_time(e['happened_at']) for e in visible if e['kind']=='delivered']
    cancelled=any(e['kind']=='cancelled' for e in visible)
    due=parse_time(order['due_at'])
    if cancelled:state='cancelled'
    elif due>cutoff:state='not_mature'
    elif not delivered:state='overdue_open'
    elif min(delivered)>due:state='delivered_late'
    else:state='on_time'
    delay=round((min(delivered)-due).total_seconds()/60) if state=='delivered_late' else 0
    overdue=round((cutoff-due).total_seconds()/60) if state=='overdue_open' else 0
    return dict(order,cohort=state,late_minutes=delay,overdue_minutes=overdue)

def aggregate(rows,keys):
    groups=defaultdict(list)
    for row in rows:groups[tuple(row[k] for k in keys)].append(row)
    result=[]
    for dims,records in sorted(groups.items()):
        counts=Counter(r['cohort'] for r in records)
        eligible=sum(counts[x] for x in ['on_time','delivered_late','overdue_open'])
        late=counts['delivered_late']+counts['overdue_open']
        result.append(dict(zip(keys,dims),total=len(records),eligible=eligible,late=late,late_rate=round(late/eligible,6) if eligible else None,on_time=counts['on_time'],delivered_late=counts['delivered_late'],overdue_open=counts['overdue_open'],cancelled=counts['cancelled'],not_mature=counts['not_mature'],delivered_late_minutes=sum(r['late_minutes'] for r in records),open_overdue_minutes=sum(r['overdue_minutes'] for r in records)))
    return result

def service_band(rate,n,rules):
    if n<rules['min_sample']:return '待补样本'
    gap=(rate-rules['late_target'])*100
    if gap>rules['priority_gap_pp']:return '优先处理'
    if gap>rules['review_gap_pp']:return '需要复核'
    return '达到阈值'
def fairness(hub_tiers,rules):
    rows=[]
    for x in hub_tiers:
        rate=x['late_rate'];rows.append(dict(hub=x['hub'],service=x['service'],eligible=x['eligible'],late=x['late'],late_rate=rate,target=rules['late_target'],gap_pp=round((rate-rules['late_target'])*100,3) if rate is not None else None,review=service_band(rate,x['eligible'],rules)))
    standardized=[]
    for hub in sorted(HUBS):
        parts=[r for r in rows if r['hub']==hub]
        enough=len(parts)==2 and all(r['eligible']>=rules['min_sample'] for r in parts)
        value=sum(r['late_rate']*rules[r['service']+'_weight'] for r in parts) if enough else None
        standardized.append(dict(hub=hub,standardized_late_rate=round(value,6) if value is not None else None,status='可比较' if enough else '待补样本'))
    return rows,standardized

def investigate(logs,policy):
    logins={};findings=[]
    for row in sorted(logs,key=lambda r:(r['at'],r['request_id'])):
        t=parse_time(row['at']);status=int(row['status']);route=urlparse(row['path']);query=parse_qs(route.query)
        if route.path=='/login' and status==200:logins[(row['user'],row['session'])]=t;continue
        if status!=200:continue
        types=[]
        if route.path=='/export' and row['role'] not in policy['export_roles']:types.append('unauthorised_export')
        hub=query.get('hub',[''])[0]
        if row['role']=='operator' and hub and hub!=policy['operator_hub'].get(row['user']):types.append('hub_scope_violation')
        login=logins.get((row['user'],row['session']))
        if login is None or (t-login).total_seconds()>policy['session_ttl_minutes']*60:types.append('expired_or_missing_session')
        if types:findings.append(dict(request_id=row['request_id'],at=row['at'],user=row['user'],session=row['session'],finding=';'.join(types),status=status,rows=int(row['rows']),bytes=int(row['bytes'])))
    return findings

def solve(data,out):
    data=Path(data);out=Path(out);out.mkdir(parents=True,exist_ok=True)
    events,rejects,dups=ingest(data)
    (out/'canonical-events.jsonl').write_text(''.join(json.dumps(e,ensure_ascii=False)+'\n' for e in events))
    writecsv(out/'rejected.csv',rejects);writecsv(out/'duplicates.csv',dups)
    rules=json.loads((data/'rules.json').read_text());cutoff=parse_time(rules['cutoff'])
    by_order=defaultdict(list)
    for e in events:by_order[e['order_id']].append(e)
    orders=readcsv(data/'orders.csv')
    records=[classify_order(o,by_order[o['order_id']],cutoff) for o in orders]
    writecsv(out/'order-review.csv',records)
    hubs=aggregate(records,['hub']);tiers=aggregate(records,['hub','service']);routes=aggregate(records,['route'])
    writecsv(out/'hub-metrics.csv',hubs);writecsv(out/'route-metrics.csv',routes)
    bands,std=fairness(tiers,rules)
    writecsv(out/'service-review.csv',bands);writecsv(out/'standardized-comparison.csv',std)
    findings=investigate(readcsv(data/'access-log.csv'),json.loads((data/'access-policy.json').read_text()))
    writecsv(out/'security-findings.csv',findings)
    summary=dict(orders=len(orders),accepted_events=len(events),rejected=len(rejects),duplicates=len(dups),cohorts=dict(Counter(r['cohort'] for r in records)),hubs=hubs,service_review=bands,standardized=std,security=dict(flagged_requests=len(findings),potential_export_rows=sum(f['rows'] for f in findings if 'unauthorised_export' in f['finding']),potential_export_bytes=sum(f['bytes'] for f in findings if 'unauthorised_export' in f['finding']),findings=findings))
    dump(out/'results.json',summary)
    return summary
if __name__=='__main__':
    import sys
    print(json.dumps(solve(sys.argv[1],sys.argv[2]),ensure_ascii=False,indent=2))

大量请求本身不是越权证据,HTTP 401 也不是泄露成功证据。

每项 20 分,14 分及以上且没有关键口径错误再进入下一次 Review。原始数据被改写、静默丢记录、分母含未到期、把 401 算成泄露、用小样本硬排站点或把前端筛选当授权,均须修正后再验收。

  • 策略与会话判断 6 分
  • 证据请求可追溯 5 分
  • 反证与影响量不夸大 5 分
  • 处置与验证可执行 4 分