Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

攻撃文字列生成 #26

Merged
merged 24 commits into from
Dec 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e2fc078
攻撃文字列の経路探索を実装
n4o847 Nov 26, 2020
5c92b18
指数的攻撃文字列
n4o847 Nov 26, 2020
50128c6
Merge branch 'master' into add_attacker
n4o847 Nov 27, 2020
3986d97
指数時間攻撃文字列(未統合)
n4o847 Nov 27, 2020
bb3e4d7
Merge branch 'redefine_state_2' into add_attacker
n4o847 Nov 27, 2020
3e3884e
State の新定義を統合
n4o847 Nov 27, 2020
5b86967
攻撃文字列末尾を修正
n4o847 Nov 27, 2020
c7f3001
eda.ts と attack.ts を統合
n4o847 Nov 27, 2020
a8c7ad0
ida.ts と統合
n4o847 Nov 27, 2020
f927951
細かい修正
n4o847 Nov 27, 2020
93d698b
攻撃文字列の suffix の探索を実装
n4o847 Nov 28, 2020
586369a
経路探索の細かい修正
n4o847 Nov 28, 2020
37e1264
Merge branch 'master' into add_attacker
n4o847 Nov 29, 2020
2db5a49
findUnacceptablePath の終了条件を修正
n4o847 Nov 29, 2020
f6f99f3
Merge branch 'master' into add_attacker
n4o847 Dec 1, 2020
85289e8
状態を新しい定義に移行
n4o847 Dec 1, 2020
9638cfa
攻撃文字列生成に NonEpsilonNFA ではなく PrunedNFA を使う
n4o847 Dec 1, 2020
6343bed
変数名変更
n4o847 Dec 1, 2020
a696007
index.ts を test.ts に揃える
n4o847 Dec 1, 2020
9337480
NonEpsilonNFA を SCCPossibleAutomaton から外す
n4o847 Dec 1, 2020
6254e5c
suffix 生成に DFA を使うように変更
n4o847 Dec 2, 2020
62af9d9
不要な関数を削除
n4o847 Dec 2, 2020
3160f4c
不要なインポートを削除
n4o847 Dec 2, 2020
a2af5c1
変数名を変更
n4o847 Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/attack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { State } from './state';
import { Char, DFA, PrunedNFA, StronglyConnectedComponentGraph } from './types';

export class Attacker {
private pnfa: PrunedNFA;
private dfa: DFA;
private nullChar: string;

constructor(pnfa: PrunedNFA, dfa: DFA) {
this.pnfa = pnfa;
this.dfa = dfa;
this.nullChar = this.getCharForNull();
}

/**
* EDA 構造を見つけ、それに対する攻撃文字列を生成する。
*/
findExponentialAttack(
scc: StronglyConnectedComponentGraph,
table: Map<State, [State, State]>,
): string | null {
for (const source of scc.stateList) {
const [sourceLeft, sourceRight] = table.get(source)!;

if (sourceLeft !== sourceRight) {
continue;
}

// 多重自己ループを検出
for (const char of scc.alphabet) {
const selfLoops = scc.transitions
.get(source, char)
.filter((q) => q === source);

if (selfLoops.length >= 2) {
let attack = '';
attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft);
{
const loop = char ?? this.nullChar;
attack += loop.repeat(20);
}
attack += this.getSuffix(sourceLeft);
return attack;
}
}

// (q1, q1) → (q2, q3) → (q1, q1) where q2 ≠ q3 を検出
for (const viaPair of scc.stateList) {
const [viaLeft, viaRight] = table.get(viaPair)!;

if (viaLeft !== viaRight && viaLeft !== sourceLeft) {
let attack = '';
attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft);
{
let loop = '';
loop += this.getPathString(sourceLeft, viaLeft);
loop += this.getPathString(viaLeft, sourceLeft);
attack += loop.repeat(20);
}
attack += this.getSuffix(sourceLeft);
return attack;
}
}
}

// EDA 検出なし
return null;
}

/**
* IDA 構造を見つけ、それに対する攻撃文字列を生成する。
*/
findPolynomialAttack(
scc: StronglyConnectedComponentGraph,
table: Map<State, [State, State, State]>,
): string | null {
// (lq, lq, rq) と (lq, rq, rq) が存在するか
for (const [lq, cq, rq] of scc.stateList.map((s) => table.get(s)!)) {
if (lq === cq && scc.stateList.includes(State.fromTriple([lq, rq, rq]))) {
let attack = '';
attack += this.getPathString(this.pnfa.initialStateSet, lq);
{
const loop = this.getPathString(lq, rq);
attack += loop.repeat(20);
}
attack += this.getSuffix(rq);
return attack;
}
}

// IDA 検出なし
return null;
}

/**
* null に対応する文字を探す。
*/
private getCharForNull(): string {
for (let c = 0x00; ; c++) {
const s = String.fromCharCode(c);
if (!this.pnfa.alphabet.has(s)) {
return s;
}
}
}

/**
* ある状態からある状態まで遷移する文字列を探す。
*/
private getPathString(
source: State | Set<State>,
destination: State,
): string {
return findPath(this.pnfa, source, destination)
.map((char) => char ?? this.nullChar)
.join('');
}

/**
* EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。
*/
private getSuffix(source: State): string {
return findPath(
this.dfa,
this.dfa.initialState,
this.pnfa.table.get(source)![1],
)
.map((char) => char ?? this.nullChar)
.reverse()
.join('');
}
}

/**
* オートマトン上である状態からある状態までの経路 (Char の配列) を幅優先探索する。
*/
function findPath(
automaton: PrunedNFA | DFA,
source: State | Set<State>,
destination: State,
): Char[] {
/** その状態にどの状態からどの遷移でたどり着けるか */
const referrer = new Map<State, [source: State, char: Char]>();

const queue: State[] = [];

const sourceSet = new Set(source instanceof Set ? source : [source]);

queue.push(...sourceSet);

while (queue.length !== 0) {
const q = queue.shift()!;
if (q === destination) {
break;
}
for (const char of automaton.alphabet) {
for (const d of automaton.transitions.get(q, char)) {
if (referrer.has(d)) {
continue;
}
referrer.set(d, [q, char]);
queue.push(d);
}
}
}

const path: Char[] = [];

for (let q = destination; !sourceSet.has(q); ) {
const [prevState, char] = referrer.get(q)!;
path.push(char);
q = prevState;
}
path.reverse();

return path;
}
45 changes: 16 additions & 29 deletions src/eda.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,26 @@
import { buildStronglyConnectedComponents } from './scc';
import { DirectProductGraph, Message } from './types';
import { PrunedNFA, DFA, DirectProductGraph, Message } from './types';
import { Attacker } from './attack';

/**
* 強連結成分を一つ一つ見ていき、EDAを持つかメッセージを返す
*/
export function showMessageEDA(dps: DirectProductGraph[]): Message {
// 別の経路で同じ文字で移動して自身に戻れたらEDA
if (dps.some((dp) => isEDA(dp))) {
return { status: 'Vulnerable', message: 'Detected EDA.' };
} else {
return { status: 'Safe', message: "Don't have EDA." };
}
}
export function showMessageEDA(
pnfa: PrunedNFA,
dfa: DFA,
dps: DirectProductGraph[],
): Message {
const attacker = new Attacker(pnfa, dfa);

function isEDA(dp: DirectProductGraph): boolean {
const sccs = buildStronglyConnectedComponents(dp);
return sccs.some((scc) => {
// 遷移元と遷移先が同じ遷移が複数存在するかを検出
for (const source of scc.stateList) {
for (const char of scc.alphabet) {
const destinationArray = scc.transitions
.get(source, char)
.filter((d) => d === source);

if (destinationArray.length >= 2) {
return true;
}
for (const dp of dps) {
const sccs = buildStronglyConnectedComponents(dp);
for (const scc of sccs) {
const attack = attacker.findExponentialAttack(scc, dp.table);
if (attack !== null) {
return { status: 'Vulnerable', message: 'Detected EDA.', attack };
}
}
}

const lrSame = scc.stateList.filter((state) => {
const [lq, rq] = dp.table.get(state)!;
return lq === rq;
});
// (n, n), (m, k) (m !== k)が存在(すべて同じじゃないが全て異なるわけではない)
return lrSame.length < scc.stateList.length && lrSame.length > 0;
});
return { status: 'Safe', message: "Don't have EDA." };
}
36 changes: 17 additions & 19 deletions src/ida.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { buildStronglyConnectedComponents } from './scc';
import { State } from './state';
import { TripleDirectProductGraph, Message } from './types';
import { PrunedNFA, DFA, TripleDirectProductGraph, Message } from './types';
import { Attacker } from './attack';

export function showMessageIDA(tdps: TripleDirectProductGraph[]): Message {
if (tdps.some((tdp) => isIDA(tdp))) {
return { status: 'Vulnerable', message: 'Detected IDA.' };
} else {
return { status: 'Safe', message: "Don't have IDA." };
}
}
export function showMessageIDA(
pnfa: PrunedNFA,
dfa: DFA,
tdps: TripleDirectProductGraph[],
): Message {
const attacker = new Attacker(pnfa, dfa);

function isIDA(tdp: TripleDirectProductGraph): boolean {
const sccs = buildStronglyConnectedComponents(tdp);
return sccs.some((scc) => {
// (lq, lq, rq) と (lq, rq, rq) が存在するか
for (const [lq, cq, rq] of scc.stateList.map((s) => tdp.table.get(s)!)) {
const state = State.fromTriple([lq, rq, rq]);
if (lq === cq && scc.stateList.includes(state)) {
return true;
for (const tdp of tdps) {
const sccs = buildStronglyConnectedComponents(tdp);
for (const scc of sccs) {
const attack = attacker.findPolynomialAttack(scc, tdp.table);
if (attack !== null) {
return { status: 'Vulnerable', message: 'Detected IDA.', attack };
}
}
return false;
});
}

return { status: 'Safe', message: "Don't have IDA." };
}
18 changes: 12 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { Parser } from 'rerejs';
import { buildEpsilonNFA } from './enfa';
import { eliminateEpsilonTransitions } from './nfa';
import { reverseNFA, determinize } from './dfa';
import { prune } from './pruning';
import { buildStronglyConnectedComponents } from './scc';
import { buildDirectProductGraphs } from './directProduct';
import { showMessageEDA } from './eda';
import { buildTripleDirectProductGraphs } from './tripleDirectProduct';
import { showMessageIDA } from './ida';
import { buildEpsilonNFA, eliminateEpsilonTransitions } from './lib';
import { buildStronglyConnectedComponents } from './scc';
import { Message } from './types';

export function detectReDoS(src: string, flags?: string): Message {
try {
const pat = new Parser(src, flags).parse();
const enfa = buildEpsilonNFA(pat);
const nfa = eliminateEpsilonTransitions(enfa);
const sccs = buildStronglyConnectedComponents(nfa);
const rnfa = reverseNFA(nfa);
const dfa = determinize(rnfa);
const pnfa = prune(nfa, dfa);
const sccs = buildStronglyConnectedComponents(pnfa);
const dps = buildDirectProductGraphs(sccs);
const messageEDA = showMessageEDA(dps);
const messageEDA = showMessageEDA(pnfa, dfa, dps);
if (messageEDA.status === 'Vulnerable') {
return messageEDA;
}
const tdps = buildTripleDirectProductGraphs(sccs, nfa);
const messageIDA = showMessageIDA(tdps);
const tdps = buildTripleDirectProductGraphs(sccs, pnfa);
const messageIDA = showMessageIDA(pnfa, dfa, tdps);
if (messageIDA.status === 'Vulnerable') {
return messageIDA;
}
Expand Down
18 changes: 9 additions & 9 deletions src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Parser } from 'rerejs';
import { buildEpsilonNFA } from './enfa';
import { eliminateEpsilonTransitions } from './nfa';
import { reverseNFA, determinize } from './dfa';
import { toDOT } from './viz';
import { buildDirectProductGraphs } from './directProduct';
import { prune } from './pruning';
import { buildStronglyConnectedComponents } from './scc';
import { buildDirectProductGraphs } from './directProduct';
import { showMessageEDA } from './eda';
import { buildTripleDirectProductGraphs } from './tripleDirectProduct';
import { showMessageIDA } from './ida';
import { prune } from './pruning';
import { toDOT } from './viz';

function main(): void {
const sources: [source: string, flags?: string][] = [
Expand Down Expand Up @@ -48,13 +48,13 @@ function main(): void {
const dfa = determinize(rnfa);
console.log(toDOT(dfa));
console.log(`//`, src, `pruned`);
const lcnfa = prune(nfa, dfa);
console.log(toDOT(lcnfa));
const sccs = buildStronglyConnectedComponents(lcnfa);
const pnfa = prune(nfa, dfa);
console.log(toDOT(pnfa));
const sccs = buildStronglyConnectedComponents(pnfa);
const dps = buildDirectProductGraphs(sccs);
console.log(`//`, src, `has EDA?: `, showMessageEDA(dps));
const tdps = buildTripleDirectProductGraphs(sccs, lcnfa);
console.log(`//`, src, `has IDA?: `, showMessageIDA(tdps));
console.log(`//`, src, `has EDA?: `, showMessageEDA(pnfa, dfa, dps));
const tdps = buildTripleDirectProductGraphs(sccs, pnfa);
console.log(`//`, src, `has IDA?: `, showMessageIDA(pnfa, dfa, tdps));
}
}

Expand Down
19 changes: 14 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export type Automaton =
| TripleDirectProductGraph;

export type SCCPossibleAutomaton =
| NonEpsilonNFA
| PrunedNFA
| DirectProductGraph
| TripleDirectProductGraph;
Expand Down Expand Up @@ -117,7 +116,17 @@ export type NullableTransition =
destination: State;
};

export type Message = {
status: 'Safe' | 'Vulnerable' | 'Error';
message: string;
};
export type Message =
| {
status: 'Safe';
message: string;
}
| {
status: 'Vulnerable';
message: string;
attack: string;
}
| {
status: 'Error';
message: string;
};