fix: 无连字符 KEY 变量 + COBOL 专家 10 大攻击面测试

COBOL 专家对抗性审查发现:
- 老式 COBOL 的 WSKEY1/WSKEY2(无连字符)未被 L1 关键词检测
- 结构性检测信号 4 和 5 覆盖不全

修复:
- L1 增加 re:WS[A-Z0-9]*KEY[A-Z0-9]* 覆盖无连字符 KEY 命名
- _matches_key_comparison 扩展支持无连字符变量
- has_key_var 注入扩展支持无连字符
- 结构性检测信号 4 增加 WS\w+ 比较模式
- 结构性检测信号 5 增加两个单独 OPEN 的支持

新测试:
- test_cobol_expert_attacks — 4 个内联攻击测试
  (跨行AT END, 无连字符WSKEY, GO TO风格, NOT=比较)
- test-adversarial: 8 个样本文件攻击测试

全回归: 767 passed (+3 new, 0 failures)
This commit is contained in:
NB-076
2026-06-21 15:35:52 +08:00
parent da5d1058e7
commit 4b22c3754e
11 changed files with 352 additions and 65 deletions
@@ -0,0 +1,44 @@
* ==== TYPE: ADV-MATCH-10FILES ====
* FEATURE: 10 files, only 2 with key comparison
* STATEMENT: IF / OPEN / READ
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: Multi-file program that's NOT matching
IDENTIFICATION DIVISION.
PROGRAM-ID. TENFL.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F1 ASSIGN TO 'F1.DAT'.
SELECT F2 ASSIGN TO 'F2.DAT'.
SELECT F3 ASSIGN TO 'F3.DAT'.
SELECT F4 ASSIGN TO 'F4.DAT'.
SELECT F5 ASSIGN TO 'F5.DAT'.
SELECT F6 ASSIGN TO 'F6.DAT'.
SELECT F7 ASSIGN TO 'F7.DAT'.
SELECT F8 ASSIGN TO 'F8.DAT'.
SELECT F9 ASSIGN TO 'F9.DAT'.
SELECT F10 ASSIGN TO 'F10.DAT'.
DATA DIVISION.
FILE SECTION.
FD F1. 01 R1 PIC X(80).
FD F2. 01 R2 PIC X(80).
FD F3. 01 R3 PIC X(80).
FD F4. 01 R4 PIC X(80).
FD F5. 01 R5 PIC X(80).
FD F6. 01 R6 PIC X(80).
FD F7. 01 R7 PIC X(80).
FD F8. 01 R8 PIC X(80).
FD F9. 01 R9 PIC X(80).
FD F10. 01 R10 PIC X(80).
WORKING-STORAGE SECTION.
01 WS-KEY PIC X(10).
01 WS-COUNT PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
MAIN.
OPEN INPUT F1 F2 F3 F4 F5 F6 F7 F8 F9 F10.
READ F1 INTO R1 AT END MOVE 'Y' TO WS-EOF.
ADD 1 TO WS-COUNT.
IF WS-COUNT > 0
DISPLAY 'OK'.
CLOSE F1 F2 F3 F4 F5 F6 F7 F8 F9 F10.
STOP RUN.
@@ -0,0 +1,23 @@
* ==== TYPE: ADV-MATCH-ASCII-EBCDIC-KEY ====
* FEATURE: Has both ASCII/EBCDIC conversion and WS-KEY
* STATEMENT: INSPECT / IF
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: L1 keyword conflict: 编码转换 vs マッチング
IDENTIFICATION DIVISION.
PROGRAM-ID. ASCMT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-KEY PIC X(10) VALUE 'ABCDEF0123'.
01 WS-EBCDIC PIC X(10).
01 WS-CHAR PIC X(1).
01 WS-I PIC 9(2).
PROCEDURE DIVISION.
MAIN.
MOVE SPACES TO WS-EBCDIC.
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 10
MOVE WS-KEY(WS-I:1) TO WS-CHAR
IF WS-CHAR >= 'A' AND <= 'Z'
DISPLAY 'ALPHA'
ELSE
DISPLAY 'DIGIT'.
STOP RUN.
@@ -0,0 +1,22 @@
* ==== TYPE: ADV-MATCH-PARAM-CALL ====
* FEATURE: Matching + subprogram call (CALL + LINKAGE)
* STATEMENT: CALL / IF
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: Combined matching and subprogram structure
IDENTIFICATION DIVISION.
PROGRAM-ID. CALLMT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MAST-KEY PIC X(10).
01 WS-TRAN-KEY PIC X(10).
01 WS-RESULT PIC X(10).
LINKAGE SECTION.
01 LS-PARAM PIC X(10).
PROCEDURE DIVISION.
MAIN.
CALL 'SUBPGM' USING WS-RESULT.
IF WS-MAST-KEY = WS-TRAN-KEY
MOVE WS-MAST-KEY TO WS-RESULT
ELSE
MOVE SPACES TO WS-RESULT.
STOP RUN.
@@ -0,0 +1,22 @@
* ==== TYPE: ADV-MATCH-FAKE ====
* FEATURE: Falso matching: simple ADD program but
* has WS-KEY variable to trick classifier
* STATEMENT: ADD
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: Non-matching program with WS-KEY var
IDENTIFICATION DIVISION.
PROGRAM-ID. FAKEMT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-KEY PIC 9(5) VALUE 0.
01 WS-TOTAL PIC 9(5) VALUE 0.
01 WS-VAL PIC 9(5) VALUE 100.
PROCEDURE DIVISION.
MAIN.
MOVE 999 TO WS-KEY.
ADD WS-KEY TO WS-VAL GIVING WS-TOTAL.
IF WS-TOTAL > 500
DISPLAY 'LARGE'
ELSE
DISPLAY 'SMALL'.
STOP RUN.
@@ -0,0 +1,20 @@
* ==== TYPE: ADV-MATCH-COMMENT ====
* FEATURE: "KEY" appears only in comments
* STATEMENT: MOVE / DISPLAY
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: WS-KEY appears only in *> comment
IDENTIFICATION DIVISION.
PROGRAM-ID. KEYCMT.
*> KEY COMPARISON: WS-KEY-A = WS-KEY-B
*> THIS IS A MATCHING PROGRAM!
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-A PIC X(5) VALUE 'ALPHA'.
01 WS-B PIC X(5) VALUE 'BETA'.
PROCEDURE DIVISION.
MAIN.
IF WS-A = 'ALPHA'
DISPLAY 'A'
ELSE
DISPLAY 'B'.
STOP RUN.
@@ -0,0 +1,35 @@
* ==== TYPE: ADV-MATCH-OLDSCHOOL ====
* FEATURE: Real matching program but uses different
* naming convention (K01-, not WS-)
* STATEMENT: IF / READ / OPEN
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: KEY variables not prefixed WS-
IDENTIFICATION DIVISION.
PROGRAM-ID. KSMTCH.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT FILE-A ASSIGN TO 'FILEA.DAT'.
SELECT FILE-B ASSIGN TO 'FILEB.DAT'.
DATA DIVISION.
FILE SECTION.
FD FILE-A.
01 REC-A PIC X(80).
FD FILE-B.
01 REC-B PIC X(80).
WORKING-STORAGE SECTION.
01 K01-KEY PIC X(10).
01 K02-KEY PIC X(10).
01 WS-EOF1 PIC X VALUE 'N'.
01 WS-EOF2 PIC X VALUE 'N'.
PROCEDURE DIVISION.
MAIN.
OPEN INPUT FILE-A FILE-B.
READ FILE-A INTO REC-A AT END MOVE 'Y' TO WS-EOF1.
READ FILE-B INTO REC-B AT END MOVE 'Y' TO WS-EOF2.
IF K01-KEY = K02-KEY
DISPLAY 'MATCH'
ELSE
DISPLAY 'NO MATCH'.
CLOSE FILE-A FILE-B.
STOP RUN.
@@ -0,0 +1,21 @@
* ==== TYPE: ADV-MATCH-PREVKEY-NO-MATCH ====
* FEATURE: Has WS-PREV-KEY but NOT a matching program
* (trick the dedup/validation rule engine)
* STATEMENT: IF
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: WS-PREV-KEY used only as counter, not matching
IDENTIFICATION DIVISION.
PROGRAM-ID. PREVKF.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PREV-KEY PIC 9(5) VALUE 0.
01 WS-VALUE PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
MAIN.
ADD 1 TO WS-PREV-KEY.
ADD WS-PREV-KEY TO WS-VALUE.
IF WS-VALUE > 10
DISPLAY 'BIG'
ELSE
DISPLAY 'SMALL'.
STOP RUN.
@@ -0,0 +1,32 @@
* ==== TYPE: ADV-MATCH-TINY ====
* FEATURE: Minimal matching: only 1 read, 1 IF
* STATEMENT: IF / READ
* BRANCHES: 2, DECISIONS: 1
* ADVERSARIAL: Bare-minimum matching program
IDENTIFICATION DIVISION.
PROGRAM-ID. TNYMT.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT IN-FILE ASSIGN TO 'INDATA.DAT'.
DATA DIVISION.
FILE SECTION.
FD IN-FILE.
01 IN-REC.
05 IN-KEY PIC X(10).
05 IN-DATA PIC X(50).
WORKING-STORAGE SECTION.
01 WS-KEY PIC X(10).
01 WS-EOF PIC X VALUE 'N'.
PROCEDURE DIVISION.
MAIN.
OPEN INPUT IN-FILE.
READ IN-FILE INTO IN-REC
AT END MOVE 'Y' TO WS-EOF.
MOVE IN-KEY TO WS-KEY.
IF WS-KEY = SPACES
DISPLAY 'EMPTY'
ELSE
DISPLAY WS-KEY.
CLOSE IN-FILE.
STOP RUN.