Skip to content

Commit

Permalink
Merge pull request #26 from n4o847/add_attacker
Browse files Browse the repository at this point in the history
攻撃文字列生成
  • Loading branch information
n4o847 authored Dec 6, 2020
2 parents 506b954 + a2af5c1 commit 1331ad0
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 68 deletions.
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;
};

0 comments on commit 1331ad0

Please sign in to comment.