Projektowanie stron WWW od podszewki

Artykuły na każdy temat

[PHP] Spam bot do phpBB2

Dodano 04.10.2012r. o 00:01
Jakiś czas temu zostałem "poproszony" o przedstawienie sytuacji jak orientacyjnie działają oprogramowania, których celem jest spamowanie for dyskusyjnych. Za przykład celu posłuży mi skrypt phpBB2. Gdyby ktoś był zainteresowany ściągnięciem (ciężko dostępnego zestawu) proponuję mój mirror (opcjonalnie mirror z załączoną polską wersją). Nie jestem pewien co do źródeł skryptu (po kilku różnych hecach), dlatego proponuję sprawdzić każdemu osobiście czy wszystko się zgadza. O ile dobrze pamiętam to paczka wykonana przez Przemo bazuje na dwójce stąd skrypt będzie działać na obu wersjach.

Dlaczego wybrałem tę wersje na mały eksperyment?

Po pierwsze ponieważ od 4 lat nie jest już wspierana i może zawierać jakieś istotne luki bezpieczeństwa, o których mi nie wiadomo. W związku z tym bardzo mało serwisów nadal go używa. Jednym z nielicznych znanych mi miejsc gdzie nadal stosuję się phpBB w wersji 2 jest forumweb.pl, nie mniej jednak nie zalecam nikomu celowania w ten serwis. Osobiście nie zamierzam tracić czasu na przeglądanie kodu pod kątem podatności na ataki, stąd nie będę podawał jakiegoś testowego adresu na swoim hoście. Lepiej po prostu zainstalujcie sobie na localhoście co trzeba i przetestujcie.

Po drugie nie jestem pozytywnie nastawiony do działalności jednostki legitymującej się pseudonimem Przemo.

Kilka słów od autora

Z góry uprzedzam, iż nie odpowiadam za "nieprawidłowe" użycie skryptu. Kiedy pisałem ten artykuł przypomniało mi się jak oglądałem pewien projekt o nazwie Forum Monitor. Dość ciekawa inicjatywa choć niekompletna. Odnośnie update phpBB2 do wersji 2.0.23 można przeczytać tutaj. Z tego co pamiętam to jedna z firm zajmujących się SEO ma swojej ofercie pakiet oprogramowania w skład którego wchodzi również coś odpowiadającego za spamowanie prywatnych wiadomości. Namiarów nie będę podawał gdyż nie chcę robić niepotrzebnej reklamy.

Kilka słów o ogólnym zamyśle

To co Wam za chwilę przedstawię jest częścią większej układanki. Ponieważ przerzucałem kod napisany w C++ i środowisku .NET do PHP to nie było to takie trudne. Do poniższego fragmentu nie dołączyłem także kontrolera/sterownika. Jeżeli macie większe zapędy to będziecie musieli sami rozbudować bazę w kierunku jaki Wam się podoba, bo ja za darmo nie udostępniam takich technologii w skład której wchodzą między innymi:
  • moduł OCR tj. "łamanie" wszelkiego rodzaju typów captchy,
  • moduł detekcyjny czyli miedzy innymi sprawdzanie na jakim skrypcie stoi strona,
  • moduł archiwizacji czyli kompletne ripowanie stron,
  • moduł statystyczno-diagnostyczny zbierający miedzy innymi wszystkie informacje o stronie lub obiektach obserwowanych,
  • moduł obserwacyjny (część sterownika głównego*) czyli zbieranie informacji na temat konkretnej frazy bądź grupy/jednostki docelowej,
  • moduł IM odpowiedzialny za zbieranie informacji np. nt. aktywności danego obiektu na poszczególnych komunikatorach. Jeżeli chodzi o komunikatory to obsługiwane są wszystkie popularne marki,
  • moduł rejestracyjny powiązany niejako z modułem OCR - tłumaczyć chyba nie trzeba?
  • moduł YT odpowiedzialny tylko i wyłącznie za tą cześć internetu,
  • moduł MB pobierający informacje o aktywności z mediów "mikroblogowych",
  • moduł SM odpowiedzialny za serwisy społecznościowe,
  • moduły tematyczne czyli takie, które odpowiadają za zbieranie informacji o aktywności z poszczególnych źródeł (np. platforma Steam),
  • moduł P2P czyli zbieranie informacji na temat aktywności poszczególnych hostów w procesie wymiany informacji (coś na kształt tego projektu).
  • i wiele innych bardzo ciekawych rzeczy Smile
Sterownik główny* odpowiedzialny jest za wymianę informacji pomiędzy poszczególnymi modułami. Dane te następnie płyną do kolejnych warstw aplikacji, gdzie w wyniku obróbki zostają zespalane, a na końcu procesu zamieniają się w raporty zbiorcze na określony temat i zostają wysyłane zainteresowanym podmiotom. Kontroler w naszym rozumowaniu jest odpowiedzialny za kolejkowanie i dbanie o priorytety zadań/"wątków". Dzięki umieszczeniu robotów sieciowych w chmurach miedzy innymi Red Dog oraz Amazonu mamy możliwości obserwacji, analizy i podejmowania działań wobec konkretnych zjawisk w czasie rzeczywistym i na masową skalę.

Kilka słów o skrypcie

Jeżeli przeczytaliście powyższą koncepcję to czas brać się za omawianie właściwego przedmiotu sporu. Tak jak mówiłem skrypt będzie działać na surowym phpBB. Jeżeli administrator jeden z drugim wprowadził jakieś modyfikację (np. SEO friendly), to będziecie musieli w swojej gestii go dostosować. Gdy redaguję ten fragment przypomina mi się kilka kwestii, które już jakiś czas temu poruszyłem i opisałem. Dla przykładu ktoś kiedyś się mnie pytał jak logować się do phpBB by Przemo. Kolejnym aspektem było pobieranie listy użytkowników, które de facto także gdzieś "opisałem". Oczywiście dla upartych można powiedzieć, że łap się DOM-a. Dla nowicjusza najfajniejszym rozwiązaniem byłoby API, które niestety nie jest udostępniane dla każdego pojedynczego forum. Na całe szczęście na FW mamy je. W kodzie występują także wstawki komentarzy typu Event log. Jeżeli chcecie zaopatrzyć tą demonstracyjną klasę w loggera to proponuję chociażby ten artykuł. Pierwotny kod był pisany dawno temu i kiedy przepisywałem go na wersje skryptową to przypomniałem sobie, iż nawet phpBB2 posiada banalne zabezpieczenia antyfloodowe, których nie da się ominąć w "normalny" (patrz źródło skryptu) sposób. Aby działania były efektywne to trzeba mieć minimum kilkanaście (w przybliżeniu 80) kont aby proces wysyłania był ciągły i nie trzeba było czekać 15 sekund (defaulowa wartość dla $board_config['flood_interval']). Oczywiście to zależy od ilości użytkowników i tego jak szybko chcemy rozesłać wiadomości. Na koniec powiem kilka prostych zdań. Nie ma się co łudzić, że wykonałbym prosty copy-pastle. Nie, nie, nie... Na to nie liczcie Smile Stąd nie trudno się domyślić, że kod jest odpowiednio zmodyfikowany aby działał tylko pod wyznaczonym kątem i był gotowy do rozszerzania działalności. Przykładowym aspektem jest sprawa pokroju chociażby braku fragmentu odpowiedzialnego za detekcje charsetu czy też wykrywania języku oskryptowania. W skrócie wygląda to banalnie. Wszystkie potrzebne informacje można dostać w sieci za darmo więc nie ma sensu płacić komuś parę złotych, aby potem przepił pod budką z piwem chyba, że ktoś lubi w ten sposób postępować bądź jest leniwy do bólu i nadziei(?) (pozdrawiam Sobaka, ahoj!).

Teraz tak: gdy mamy już postawione testowe forum dyskusyjne oparte o phpBB2 to potrzebujemy kilku kont testowych. W tym celu musimy je stworzyć. Oczywiście tworzenie tego manualnie mija się z celem wszak jesteśmy programistami, prawda? Naszym celem jest... no właśnie co? Rozwiązywanie cudzych problemów? Tutaj sobie dodaj jeszcze parę zdań po cichu aby rodzice i/lub rodzeństwo/domownicy/lokatorzy/zgredy nie usłyszeli. Do rzeczy! Aby upchnąć parę kont na forum odpalamy sobie taki skrypcik, który musicie oczywiście odpowiednio skonfigurować.
Kod:
<?php
// Ify wyparowały
// set_time_limit(0);
mysql_connect('localhost''root''');
mysql_select_db('forum');

// Tworzymy 0xFF kont testowych - mandolina?
for($h 1$h <= 0xFF; ++$h)
{
 // login -> test.$h | password -> test (098f6bcd4621d373cade4e832627b4f6) | mail -> test.$h@test.pl
 $sql "INSERT INTO phpbb_users VALUES (".($h 2).", 1, 'test".$h."', '098f6bcd4621d373cade4e832627b4f6', 1349079485, 0, 1349051396, ".(1349051344 $h).", 0, 0, 0.00, 1, 'polish', 'D M d, Y g:i a', 0, 0, 0, 0, 0, NULL, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, '', 0, 'test".$h."@test.pl', '', '', '', '', '', '', '', '', '', '', '0130880e083dc9bfd270', NULL)";

 mysql_query($sql);
}

mysql_close();
?>
Pamiętać o prefiksie jeżeli się zmieniało, bo o danych do db nie wspomnę!

Testowy plik startowy wygląda tak:
Kod:
<?php
include './beithir.class.php';
// Coffee cream
set_time_limit(0);
// ini_set('memory_limit', '-1');
// Tworzymy Belzebuba
$bot = new Beithir();
$bot -> data['address'] = 'http://127.0.0.1/forum/';
$bot -> data['forum_type'] = 1// phpBB version 2.0.x
$bot -> data['language'] = 1// PL
// Unnecessary :]
// $bot -> detect_forum_type();
// $bot -> detect_forum_language();

// Na wszelki wypadek gdyby wymagało zalogowania
if($bot -> login_as('test1''test'))
{
 // $exceptions_list powinna zawierać listę ID i/lub loginów, których ma nie zbierać
 $list $bot -> get_memberlist(array('id' => array(3), 'logins' => array())); // Właściwie niepotrzebne skoro znamy no ale...
 $x $bot -> send_pm($list, array('subject' => 'Test''content' => 'Czytaj więcej wujka google drogi [b]{login}[/b] Smile'));
 $bot -> eat_the_cookie(); // "Log out"

 echo 'Wysłano '.$x.' wiadomości prywatnych';
}
else
{
 // Event log
}
?>
Natomiast klasa wygląda tak:
Kod:
<?php
class Beithir
{
 public $data = array('pattern_profile_link' => array(=> '#<a.*href="profile\.php\?mode=viewprofile&amp;u=(\d+)".*>(.+)<\/a>#'), 'pattern_pm_redirect' => array(=> '#.+(<meta http-equiv="refresh" content="\d+;url=privmsg\.php\?folder=inbox">{1}).+#s'), 'flood_interval' => array(=> 15));
 public $lang = array(=> array('Zaloguj''Wyślij'));

 // Ciach babkę w piach

 public function login_as($login$password)
 {
  while(1)
  {
   $cookie_name '/data/cookies/'.uniqid();

   if(!file_exists($cookie_name))
   {
    $this -> data['cookie'] = array('.'.$cookie_namedirname(__FILE__).($this -> is_win() ? str_replace('/''\\'$cookie_name) : $cookie_name));
    break;
   }
  }

  switch($this -> data['forum_type'])
  {
   case 1:
   case 2:
    $post = array('username' => $login'password' => $password'redirect' => '''login' => $this -> lang[$this -> data['language']][0]);
   break;

   // Ciach
  }

  if($post)
  {
   $ch curl_init($this -> data['address'].'login.php');
   curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
   curl_setopt($chCURLOPT_HEADERfalse);
   curl_setopt($chCURLOPT_COOKIEFILE$this -> data['cookie'][1]);
   curl_setopt($chCURLOPT_COOKIEJAR$this -> data['cookie'][1]);
   // Ucięto wysyłanie nagłówków (browser, lang, etc.)
   curl_setopt($chCURLOPT_POSTFIELDS$this -> prepare_request($post));
   curl_exec($ch);
   $headers curl_getinfo($ch);
   curl_close($ch);

   return ($headers['http_code'] == 302 && $this -> is_redirected($headers['redirect_url'], 'index.php'));
  }
  else
  {
   return false;
  }
 }

 public function get_memberlist($exceptions_list$regex null)
 {
  switch($this -> data['forum_type'])
  {
   case 1:
   case 2:
    $base_url $this -> data['address'].'memberlist.php?mode=joined&order=desc&start=';
   break;

   // Ciach
  }

  if($base_url)
  {
   $h 0;

   while(1)
   {
    // Ciupamy ID i loginy
    $ch curl_init($base_url.$h);
    curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
    curl_setopt($chCURLOPT_HEADERfalse);
    curl_setopt($chCURLOPT_COOKIEFILE$this -> data['cookie'][1]);
    curl_setopt($chCURLOPT_COOKIEJAR$this -> data['cookie'][1]);
    // Ucięto wysyłanie nagłówków (browser, lang, etc.)
    $content curl_exec($ch);
    $headers curl_getinfo($ch);
    curl_close($ch);

    if($headers['http_code'] == 200)
    {
     // Dla phpBB w wersji 2 loginem może być cokolwiek czyli właściwie kropka z małymi wyjątkami (patrz źródła skryptu -> np. functions_validate.php)
     preg_match_all($this -> data['pattern_profile_link'][$this -> data['forum_type']], $content$profiles);

     if(count($profiles[0]) > 0)
     {
      for($i 0$how count($profiles[0]); $i $how; ++$i)
      {
       if(!is_int(array_search($profiles[1][$i], $exceptions_list['id'])) && !is_int(array_search($profiles[2][$i], $exceptions_list['logins'])))
       {
        $list[] = array((int)$profiles[1][$i], $profiles[2][$i]);
       }
      }
     }
     else
     {
      break;
     }

     $h += 50;
     unset($catch_error);
    }
    else
    {
     if($catch_error 2)
     {
      // Event log
      break;
     }
     else
     {
      // Event log
      ++$catch_error;
      sleep(3);
     }
    }
   }

   return $list;
  }
  else
  {
   return false;
  }
 }

 public function send_pm($list$input)
 {
  switch($this -> data['forum_type'])
  {
   case 1:
   case 2:
    $post = array('subject' => $input['subject'], 'post' => $this -> lang[$this -> data['language']][1], 'sid' => $this -> data['sid']);
   break;

   // Ciach
  }

  foreach($list as $target)
  {
   $post['message'] = str_replace(array('{login}' /*, ciach*/), array($target[1/*, ciach*/), $input['content']);
   $post['username'] = $target[1];

   while(1)
   {
    $ch curl_init($this -> data['address'].'privmsg.php?mode=post&u='.$target[0]);
    curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
    curl_setopt($chCURLOPT_HEADERfalse);
    curl_setopt($chCURLOPT_COOKIEFILE$this -> data['cookie'][1]);
    curl_setopt($chCURLOPT_COOKIEJAR$this -> data['cookie'][1]);
    // Ucięto wysyłanie nagłówków (browser, lang, etc.)
    curl_setopt($chCURLOPT_POSTFIELDS$this -> prepare_request($post));
    $content curl_exec($ch);
    $headers curl_getinfo($ch);
    curl_close($ch);

    if($headers['http_code'] == 200 && $this -> is_redirected(''''$content))
    {
     ++$counter;
     break;
    }
    else
    {
     if($catch_error 2)
     {
      // Event log
      $service_disabled true;
      break;
     }
     else
     {
      // Event log
      ++$catch_error;
      sleep(3);
     }
    }
   }

   if($service_disabled)
   {
    break;
   }

   unset($catch_error);
   // Dla phpBB2 wynosi 15 sekund
   sleep($this -> data['flood_interval'][$this -> data['forum_type']]);
  }

  return $counter;
 }

 public function eat_the_cookie()
 {
  return unlink($this -> data['cookie'][1]);
 }

 public function prepare_request($query)
 {
  if(!empty($query) && is_array($query)) // Nie zapomnij o isset() Ty rygorystyczny draniu!
  {
   foreach($query as $key => $value)
   {
    $query[$key] = urlencode($key).'='.urlencode($value);
   }

   return implode('&'$query);
  }
  else
  {
   return false;
  }
 }

 // Wyjęte z rdzenia skryptu
 private function is_win()
 {
  return php_uname('s') == 'Windows NT';
 }

 private function is_redirected($url$file$content '')
 {
  if($content)
  {
   preg_match($this -> data['pattern_pm_redirect'][$this -> data['forum_type']], $content$redirect);

   return (bool)$redirect[1];
  }
  else
  {
   return is_int(strpos($url$this -> data['address'].$file)) && $this -> get_sid($url);
  }
 }

 private function get_sid($url)
 {
  preg_match('#.?sid\=([a-z0-9]{32}).?#'$url$sid);

  if($sid[1])
  {
   $this -> data['sid'] = $sid[1];

   return true;
  }
  else
  {
   return false;
  }
 }

 // Ciach
}
?>
Dla opcji leniwej polecam po prostu uderzyć do działu skrypty.

Komentarze

Brak komentarzy

Dodaj komentarz

Zostaw komentarz jeżeli możesz! Nie bądź przysłowiowym botem! Nie bądź obojętny! Ciebie to nic nie kosztuje, a mi sprawi uśmiech na twarzy.
Zezwolono używać: BBCode
Zabroniono używać:
znaczników HTML

(Wymagany)

(Wymagany, niepublikowany)

(Nie wymagana)

Token:

Obrazek dla bota

(Przepisz tylko cyfry!)

(Wymagana)