Fake Recognition mit Dijkstra

by mlaug on 25. Juli 2010

Wenn man in seinem Webshop Barzahlung zulässt, besteht häufig das Problem die Bestellung zu verifizieren. Hierbei kann man auf Hürden setzen wie Captchas, die in der Regel aber nur Bots abhalten. Wenn jemand wirklich vor hat sich einen Spass zu gönnen, dann lässt er sich von so was eher weniger abschrecken. Hält sich meist vielleicht sogar für besonders geschickt, wenn er das alles hinter sich gebracht hat. In diesem Moment ist ein Script Kiddie im Geiste geboren. Glückwunsch!

Wie kann ich also eine Bestellung als Fake erkennen, bevor diese an einen Subanbieter weitergeleitet wird?

Mein Lösungsansatz basiert auf dem Dijkstra Algoritmus, der den kürzesten Weg durch ein Netz findet. Mein Netz spannt hierbei über die Tastatur, wobei die Nachbarn eines jeden Buchstaben über Pfade erreichbar sind. Was soll das ganze bringen? Meine Theorie basiert darauf, dass jemand, der eine nicht ernst gemeinte Bestellung abgibt keinen richtigen Namen eingibt. Viele hacken einfach auf die Tastatur ein und erzeugen dabei Ergebnisse wie folgt:

  • asdasd
  • qweqwe
  • lkjlkj

Diese Buchstaben liegen nun sehr nah bei einander. Daher gilt hier: kürzestPfadDurchDieTastatus(Wort) + 1 == Länge(Wort). Hier könnte man noch einen Threshold einbauen, der gewisse Abweichungen erlaubt, woraus sich final folgende Formel ergibt kürzestPfadDurchDieTastatus(Wort)  + THRESHOLD <= Länge(Wort). Genug der Theorie, gehen wir zur Umsetzung über:

Das benötigte Netz definiere ich primitiv erstmal mit einem array (dies basiert auf dem deutschen Layout einer Tastatur):

        //define keyboard neighbors (german layout)
        $neighbors = array(
            'q' =&gt; array('w','a','s'),
            'w' =&gt; array('q','e','d','s','a'),
            'e' =&gt; array('r','f','d','s','w'),
            'r' =&gt; array('e','t','d','f','g'),
            't' =&gt; array('r','z','f','g','h'),
            'z' =&gt; array('t','u','g','h','j'),
            'u' =&gt; array('z','h','j','k','i'),
            'i' =&gt; array('u','j','k','l','o'),
            'o' =&gt; array('i','k','l','ö','p'),
            'p' =&gt; array('o','l','ö','ä','ü'),
            'ü' =&gt; array('p','ö','ä'),
            'a' =&gt; array('q','w','s','y'),
            's' =&gt; array('a','q','w','e','d','x','y'),
            'd' =&gt; array('w','e','r','f','c','x','s'),
            'f' =&gt; array('e','r','t','g','v','c','d'),
            'g' =&gt; array('r','t','z','h','b','v','f'),
            'h' =&gt; array('t','z','u','j','n','b'),
            'j' =&gt; array('h','z','u','i','k','m','n'),
            'k' =&gt; array('j','u','i','o','l','m'),
            'l' =&gt; array('k','i','o','p','ö'),
            'ö' =&gt; array('l','p','ü','ä'),
            'y' =&gt; array('a','s','x'),
            'x' =&gt; array('y','s','d','c'),
            'c' =&gt; array('x','d','f','v'),
            'v' =&gt; array('c','f','g','v'),
            'b' =&gt; array('v','g,','h','n'),
            'n' =&gt; array('b','h','j','m'),
            'm' =&gt; array('n','j','k'),
            'ä' =&gt; array('ö','ü')
        );

Jeden Pfad sollten wir nun mit dem Aufwand 1 definieren, da es zwischen den Pfaden keine Unterschiede gibt und es auch keine Pfade mit der Länge > 1 gibt! Jeder Buchstabe kann nur seine Nachbarn erreichen.

        $points = array();
        $map = array();
        //generate routes
        foreach($neighbors as $letter =&gt; $n){
            $points[] = array($letter,$n,1);
        }

Das Netz wird nun durch ein mehrdimensionales Array dargestellt

        //generate map
        foreach ($points as $point) {
            $x = $point[0];
            $y = $point[1];
            $c = $point[2];
            $map[$x][$y] = $c;
            $map[$y][$x] = $c;
        }
 
        //init map
        for ($i=0; $i &lt; count($map); $i++) {
            $map[$i][$i] = 0;
        }

Die Vorverarbeitung hätten wir hier beendet. Alles was jetzt noch passieren muss ist die Verwendung des Dijkstra Algoritmus. Hierbei setze ich auf eine Implementierung von GisWiki.

class Dijkstra {
 
	var $visited = array();
	var $distance = array();
	var $previousNode = array();
	var $startnode =null;
	var $map = array();
	var $infiniteDistance = 0;
	var $bestPath = 0;
	var $matrixWidth = 0;
 
	function Dijkstra(&amp;$ourMap, $infiniteDistance) {
		$this -&gt; infiniteDistance = $infiniteDistance;
		$this -&gt; map = &amp;$ourMap;
		$this -&gt; bestPath = 0;
	}
 
	function findShortestPath($start,$to = null) {
		$this -&gt; startnode = $start;
		foreach (array_keys($this-&gt;map) as $i) {
			if ($i == $this -&gt; startnode) {
				$this -&gt; visited[$i] = true;
				$this -&gt; distance[$i] = 0;
			} else {
				$this -&gt; visited[$i] = false;
				$this -&gt; distance[$i] = isset($this -&gt; map[$this -&gt; startnode][$i])
					? $this -&gt; map[$this -&gt; startnode][$i]
					: $this -&gt; infiniteDistance;
			}
			$this -&gt; previousNode[$i] = $this -&gt; startnode;
		}
 
		$maxTries = count($this-&gt;map);
		for ($tries = 0; in_array(false,$this -&gt; visited,true) &amp;&amp; $tries &lt;= $maxTries; $tries++) { 			$this -&gt; bestPath = $this-&gt;findBestPath($this-&gt;distance,array_keys($this -&gt; visited,false,true));
			if($to !== null &amp;&amp; $this -&gt; bestPath === $to) {
				break;
			}
			$this -&gt; updateDistanceAndPrevious($this -&gt; bestPath);
			$this -&gt; visited[$this -&gt; bestPath] = true;
		}
	}
 
	function findBestPath($ourDistance, $ourNodesLeft) {
		$bestPath = $this -&gt; infiniteDistance;
		$bestNode = 0;
		foreach ($ourNodesLeft as $node) {
			if($ourDistance[$node] &lt; $bestPath) { 				$bestPath = $ourDistance[$node]; 				$bestNode = $node; 			} 		} 		return $bestNode; 	} 	function updateDistanceAndPrevious($obp) { 		foreach (array_keys($this-&gt;map) as $i) {
			if( 	isset($this-&gt;map[$obp][$i])
			    &amp;&amp;	($this-&gt;map[$obp][$i] != $this-&gt;infiniteDistance || $this-&gt;map[$obp][$i] == 0 )
				&amp;&amp;	($this-&gt;distance[$obp] + $this-&gt;map[$obp][$i] &lt; $this -&gt; distance[$i])
			)
			{
					$this -&gt; distance[$i] = $this -&gt; distance[$obp] + $this -&gt; map[$obp][$i];
					$this -&gt; previousNode[$i] = $obp;
			}
		}
	}
 
	function printMap(&amp;$map) {
		$placeholder = ' %' . strlen($this -&gt; infiniteDistance) .'d';
		$foo = '';
		for($i=0,$im=count($map);$i&lt;$im;$i++) {
			for ($k=0,$m=$im;$k&lt;$m;$k++) { 				$foo.= sprintf($placeholder, isset($map[$i][$k]) ? $map[$i][$k] : $this -&gt; infiniteDistance);
			}
			$foo.= "\n";
		}
		return $foo;
	}
 
	function getResults($to = null) {
		$ourShortestPath = array();
		$foo = '';
		foreach (array_keys($this-&gt;map) as $i) {
			if($to !== null &amp;&amp; $to !== $i) {
				continue;
			}
			$ourShortestPath[$i] = array();
			$endNode = null;
			$currNode = $i;
			$ourShortestPath[$i][] = $i;
			while ($endNode === null || $endNode != $this -&gt; startnode) {
				$ourShortestPath[$i][] = $this -&gt; previousNode[$currNode];
				$endNode = $this -&gt; previousNode[$currNode];
				$currNode = $this -&gt; previousNode[$currNode];
			}
			$ourShortestPath[$i] = array_reverse($ourShortestPath[$i]);
			if ($to === null || $to === $i) {
			if($this -&gt; distance[$i] &gt;= $this -&gt; infiniteDistance) {
				$foo .= sprintf("no route from %d to %d. \n",$this -&gt; startnode,$i);
			} else {
				$foo .= sprintf('%d =&gt; %d = %d [%d]: (%s).'."\n" ,
						$this -&gt; startnode,$i,$this -&gt; distance[$i],
						count($ourShortestPath[$i]),
						implode('-',$ourShortestPath[$i]));
			}
			$foo .= str_repeat('-',20) . "\n";
				if ($to === $i) {
					break;
				}
			}
		}
		return $foo;
	}
} // end class

Im folgenden Wird ein eingegebenes Wort einfach überprüft und mit der Länge des Wortes selbst überprüft. Stimmen diese beiden Werte überein oder sind sich sehr nah, ist die Wahrscheinlichkeit sehr hoch, dass es sich hierbei um ein verdächtiges Wort handelt.

        $wort = "asd";
        $elems = str_split($wort);
        $from = null;
        $to = null;
        $result = 0;
        for($i=0;$i&lt;count($elems)-1;$i++){
            $from = $elems[$i];
            $to = $elems[$i+1];
            $result += $dijkstra-&gt;getResult($from,$to);
        }

Das Ergebnis, gespeichert in der Variable $result, wäre hier 2 (von a -> b (1), dann von b->d (1) ). Nach der Formel, die ich oben aufgestellt habe, wäre dieses Wort höchst verdächtig. Zu Recht! Ich hoffe ich konnte jemanden mit diesem Ansatz unterstützen seine Fake Bestellungen zu verringern. Weiterhin wäre ich natürlich glücklich über jeden Hinweis, wie ihr mit solchen Bestellungen umgeht!

Leave a Comment

{ 1 trackback }