Tag Archives: gist

webmastering

2 ways to check stream mimetype

This entry is the first of three parts cove­ring stre­ams in PHP. I’m prepa­ring for ZCE certi­fi­ca­tion and stre­ams are impor­tant part to pass the exam. At least I think so…

Now to the point.  By using stream conte­xts we can „hook up” to stream noti­fi­ca­tions by stream_context_set_params func­tion. When you want to know what’s happe­ning during stream proces­sing live, the best way is to use stream context noti­fi­ca­tion param. Take a look at the simplest snip­pet:

Notice that even when we didn’t start fetching stream contents yet, a few inte­re­sting things happened:

  • stream has connected,
  • retrie­ved mime type,
  • redi­rect took place (not always),
  • retrie­ved file size (not always),
  • noti­fied 0 bytes progress.

Also notice that this method won’t work with local reso­ur­ces (at least on my server), what brings us to second method invo­lving stream reading:

This method will always work under 2 conditions:

  • you fetched enough bytes (for eg. Adobe PDF full reco­gni­tion needs some­ti­mes about 10 bytes),
  • you check mime­type with first pass (it’s obvious) or you end up with octet stream generics.

Stay tuned for second part about custom stream filters.

linux webmastering

(W miarę) bezpieczne uruchamianie skryptów PHP poprzez shell_exec()

Zgod­nie z obiet­nicą dzisiaj część doty­cząca bezpiecz­nego urucha­mia­nia zewnętrz­nych skryp­tów PHP. Od razu mówię, że nie jestem jakimś guru dot. zabez­pie­czeń syste­mów Unix. Zebra­łem rozsy­pane po inter­ne­cie infor­ma­cje i spró­bo­wa­łem złożyć to w całość.

Gist został uaktu­al­niony o klasę ICMPPingProcesserrzuć­cie okiem. Posłuży jako baza do naszych działań.

Jak już wspo­mnia­łem w poprzed­nim wpisie, w Linuk­sie nie można wyko­nać pole­ce­nia socket_create() na typo­wym użyt­kow­niku Apache, czyli www-data. Aby obejść ten problem skorzy­sta­łem z pole­ce­nia posix_seteuid(0) zmie­nia­ją­cego użyt­kow­nika procesu na roota na czas życia obiektu ICMPPing. Z kolei aby wyko­nać to pole­ce­nie musimy stać się rootem :-). Z termi­nala nic trudnego:

sudo php icmp.php http://spiechu.pl

Zosta­niemy zapy­tani o hasło i dosta­jemy odpo­wiedź skryptu. Problem poja­wia się gdy chcemy wywo­łać pole­ce­nie z poziomu innego skryptu PHP. W Ubuntu nie da się/nie umiem (a próbo­wa­łem na wiele sposo­bów) wpisać hasła dla sudo z poziomu lini komend. Wobec tego trzeba zmusić sudo aby nie pytał o hasło. Oczy­wi­ście opcja aby nadać użyt­kow­ni­kowi www-data wszyst­kie upraw­nie­nia roota na stałe nie wcho­dzi w grę. Trzeba maksy­mal­nie zawę­zić „pole manewru” dla www-data.

Wobec tego przy­cze­piamy się do pliku /etc/sudoers, który prze­cho­wuje upraw­nie­nia zwią­zane z pole­ce­niem sudo. Przy­po­mi­nam, że plik sudo­ers należy edyto­wać wyłącz­nie za pomocą pole­ce­nia visudo, a więc w terminalu:

sudo visudo

i jedziemy z edycją:

# Ustawiamy kilka aliasow
# gdy skryptow zrobi sie sporo
# bedzie latwiej zarzadzac tym balaganem
User_Alias APACHE_USER = www-data
Runas_Alias ROOT_USER = root
Host_Alias PHP_MACHINE = dave-ubunciak
Cmnd_Alias ICMP_SCRIPT = /usr/bin/php /home/dave/icmp.php http\://*
 
# Wlasciwe polecenie
APACHE_USER PHP_MACHINE = (ROOT_USER) NOPASSWD: ICMP_SCRIPT

Pole­ce­nie można prze­tłu­ma­czyć tak:
Użyt­kow­nik www-data na maszy­nie dave-ubunciak jako root może wyko­nać skrypt php o nazwie icmp.php bez hasła z para­me­trem rozpo­czy­na­ją­cym się od http://. Wszel­kie odstęp­stwa będą trak­to­wane komu­ni­ka­tem sudo: no tty present and no askpass program speci­fied Sorry, try again.
Od tego momentu możemy w dowol­nym skryp­cie wywoływać:

// na czas developmentu warto na koncu polecenia wpisac
// 2>&1 dzieki czemu strumien stderr przekierujemy na wyjscie
$process = shell_exec('sudo php /home/user/icmp.php http://www.spiechu.pl');
echo $process;

Co jesz­cze można zrobić z samym plikiem icmp.php? Możemy zmie­nić mu użyt­kow­nika i grupę na root i ogra­ni­czyć możli­wość jego wyko­na­nia wyłącz­nie do roota, czyli:

sudo chown root icmp.php
sudo chgrp root icmp.php
sudo chmod 400 icmp.php

Na koniec przy­cze­pię się do samego icmp.php. Napi­sa­łem klasę ICMPPingProcesser, która spraw­dza czy podany adres www jest prawi­dłowy i odsiewa wszystko poza podsta­wo­wym adre­sem hosta. Poni­żej przy­ta­czam w całości:

class ICMPPingProcesser
{
    /**
     * @var string
     */
    protected $urlAddress;
 
    /**
     * @param string $urlAddress
     */
    public function __construct($urlAddress)
    {
        $this->urlAddress = $urlAddress;
    }
 
    /**
     * Returns 'Trying {$urlAddress}: PING RESPONSE: Everything OK'
     * when url address replied correctly
     *
     * @return string
     */
    public function ping()
    {
        try {
            $message = '';
            $urlToPing = $this->processUrl($this->urlAddress);
            if (!$this->isUrlExists($urlToPing)) {
                throw new Exception("{$urlToPing} doesn't exist!");
            }
            $icmp = new ICMPPing();
            $respond = $icmp->sendPacket($urlToPing, 'Everything OK');
            $message = "Trying {$urlToPing}: ";
            $message .= "PING RESPONSE: {$icmp->analyzeRespond($respond)}";
 
            return $message;
        } catch (Exception $e) {
            $message .= $e->getMessage();
 
            return $message;
        }
    }
 
    /**
     * Returns sanitized url host from param
     *
     * @param  string    $urlAddress
     * @return string    url host
     * @throws Exception when url address is not valid
     */
    protected function processUrl($urlAddress)
    {
        $sanitizedUrl = filter_var($urlAddress, FILTER_SANITIZE_URL);
        if ($sanitizedUrl === false || filter_var($sanitizedUrl, FILTER_VALIDATE_URL) === false) {
            throw new Exception("{$urlAddress} is not valid URL");
        }
 
        return parse_url($sanitizedUrl, PHP_URL_HOST);
    }
 
    /**
     * Checks if url address exists
     *
     * @param  string  $urlAddress
     * @return boolean
     */
    protected function isUrlExists($urlAddress)
    {
        if (gethostbyname($urlAddress) === $urlAddress) {
            return false;
        }
 
        return true;
    }
 
}
 
if (PHP_SAPI === 'cli' && isset($_SERVER['argv'][1])) {
    $pingProcesser = new ICMPPingProcesser($_SERVER['argv'][1]);
    echo $pingProcesser->ping();
} else {
    echo 'Not in cli mode or agument not set';
}

Ostat­nie kilka lini­jek spraw­dza czy skrypt odpa­lany jest w trybie lini komend i czy istnieje jakiś argument.

Reasu­mu­jąc:

  1. Właści­cie­lem pliku powi­nien być root i tylko root powi­nien mieć prawo odczy­tać zawartość.
  2. W /etc/sudoers prawo wyko­na­nia skryptu powinno być maksy­mal­nie zawę­żone (żadnych ALL).
  3. Skrypt przed wyko­na­niem powi­nien dokład­nie spraw­dzać podane argumenty.
webmastering

Tworzenie pakietów ICMPPHP

Od tygo­dnia na poważ­nie wzią­łem się za rozpra­co­wy­wa­nie proto­ko­łów siecio­wych. Dzisiaj scho­dzimy na poziom najniż­szy jaki się da w progra­mo­wa­niu, czyli zer i jedy­nek. Ja jako osoba za 2 miesiące rozpo­czy­na­jąca zawo­dowo przy­godę z progra­mo­wa­niem (ojej, wydało się ;-) ) muszę mieć dosyć dobre poję­cie jak to się dzieje, że wpisuję adres www w pasku adresu prze­glą­darki, naci­skam enter i poka­zuje się Fejs­buk. Oczy­wi­ście zwykli użyt­kow­nicy inter­netu nie muszą wiedzieć jak to dokład­nie działa w myśl zasady „nie muszę być mecha­ni­kiem samo­cho­do­wym żeby jeździć autem”.

Wobec tego na warsz­tat wzią­łem na począ­tek rzecz dosyć prostą: ICMP, a nawet tylko jego wyci­nek pod nazwą Echo request/Echo reply. Komu­ni­katy ICMP stano­wią podstawę dzia­ła­nia inter­netu. Każda maszyna podłą­czona do sieci potrafi zada­wać pyta­nia i otrzy­my­wać odpo­wie­dzi w postaci ICMP. Tak naprawdę zdubluję funk­cjo­nal­ność wbudo­waną w każdy system opera­cyjny pod powszech­nie znaną nazwą ping.

Ręczne tworze­nie pakie­tów w PHP nie ma oczy­wi­ście więk­szego sensu poza eduka­cyj­nym (no chyba, że tworzymy coś niety­po­wego lub dosto­so­wu­jemy się do już istnie­ją­cego proto­kołu). Jeśli potrze­bu­jemy coś „pingnąć”, wywo­łu­jemy pole­ce­nie ping shella z poziomu skryptu PHP i tyle.

W Gist zamie­ści­łem klasę gotową do użytku. Na Linuk­sie będzie problem z jej odpa­le­niem, gdyż bez roota nie wywo­łamy funk­cji socket_create(). Ten temat jest na tyle ciekawy, że zosta­wię sobie na następny wpis.

Sama struk­tura proto­kołu jest dosyć prosta. Mamy 6 elementów:

  • 8 bitów typ żąda­nia (0x08 w przy­padku echo requ­est, 0x00 w przy­padku echo reply),
  • 8 bitów kod żąda­nia (0x00),
  • 16 bitów suma kontro­lna (tzw. inter­net check­sum, o którym trochę niżej),
  • 16 bitów iden­ty­fi­ka­tor (losowa liczba z zakresu 0x000 - 0xFFFF),
  • 16 bitów nr sekwen­cyjny (0x00),
  • 8– bitów dane (znaki ASCII).

Operu­jąc nisko­po­zio­mowo musimy zaprzy­jaź­nić się z funk­cjami pack()unpack() w celu stwo­rze­nia i odczytu repre­zen­ta­cji bito­wej ciągu. W przy­padku ICMP będzie to wyglą­dało tak:

const PACKET_REQUEST_TEMPLATE = 'CCnnnA*';
const PACKET_RESPOND_TEMPLATE = 'Ctype/Ccode/nchecksum/nuid/nseq/A*message';

Widać, że 1 bajt możemy odzwier­cie­dlić w postaci typu unsi­gned char, a 2 bajty w postaci unsi­gned short.

Teraz może nasu­nąć się pyta­nie jak wypeł­nić pole check­sum, skoro jest gdzieś w środku? Odpo­wiedź jest prosta: w pole wpisu­jemy tymcza­sowo 0x00, obli­czamy sumę kontro­lną dla cało­ści, a następ­nie zastę­pu­jemy wyni­kiem. Jest to dokład­nie 3 i 4 bajt. Jak obli­czyć sumę kontro­lną? Jest do tego RFC z 1988 r. pod nazwą Compu­ting the Inter­net Check­sum. Jednak zanim znie­chę­ci­cie się suchym żargo­nem infor­ma­tycz­nym wytłu­ma­czę po ludzku:

  1. Cały pakiet ćwiar­tu­jemy na kawałki po 16 bitów,
  2. sumu­jemy wszystko,
  3. jeśli w wyniku powstaje nam jakaś nadwyżka w postaci 17 i więcej bitu, odci­namy ją, a następ­nie doda­jemy do pozo­sta­łych 16tu,
  4. ponow­nie spraw­dzamy czy nie powstała nam nadwyżka,
  5. odwra­camy wynik.

Zapis w PHP:

protected function computeChecksum($packet) {
 
  // treat the whole packet as 16 bits unsigned short integers
  $seqPer16bits = unpack('n*', $packet);
  $sum = array_sum($seqPer16bits);
 
  // if there is a carry above 16 bit, add it at the beginning
  $sum = ($sum >> 16) + ($sum & 0xFFFF);
 
  // double check if there is no new carry after previous addition
  $sum += ($sum >> 16);
 
  // return 16 bits negate
  return pack('n', ~$sum);
}

Jak „wkleić” wynik w dobre miej­sce? Mała sztuczka:

$packet[2] = $checkSum[0]; // 3 bajt pakietu
$packet[3] = $checkSum[1]; // 4 bajt pakietu

Następ­nie możemy sobie stwo­rzyć gniazdo i usta­wić jakiś timeout:

$this->_socket = socket_create(
  AF_INET, SOCK_RAW, getprotobyname('icmp'));
 
socket_set_option(
  $this->_socket, SOL_SOCKET, SO_RCVTIMEO, array(
    'sec' => 1,
    'usec' => 0));

Jeste­śmy gotowi do wysyłki:

public function sendPacket($destination, $message = self::DEFAULT_MSG, $port = 0) {
  $packet = $this->getNewPacket($message);
  socket_sendto(
    $this->_socket, $packet, strlen($packet), 0, $destination, $port);
 
  $respond = 0;
  socket_recvfrom($this->_socket, $respond, 255, 0, $destination, $port);
 
  // strip IP header
  return substr($respond, 20);
}

Jeśli odbie­ramy pakiet, warto poddać go analizie:

public function analyzeRespond($responsePacket) {
  $unpackedRespond = unpack(self::PACKET_RESPOND_TEMPLATE, $responsePacket);
  if ($unpackedRespond['type'] !== self::TYPE_RESPONSE) {
    throw new Exception('Bad response type');
  }
 
  if ($unpackedRespond['uid'] !== $this->_uid) {
    throw new Exception('Bad unique id');
  }
 
  // set 3rd and 4th checksum byte to 0x00
  // in order to calculate correct checksum
  $responsePacket[2] = pack('C', self::INITIAL_CHECKSUM);
  $responsePacket[3] = pack('C', self::INITIAL_CHECKSUM);
  if (pack('n*', $unpackedRespond['checksum']) !== $this->computeChecksum($responsePacket)) {
    throw new Exception('Bad checksum');
  }
  return $unpackedRespond['message'];
}

Zdaję sobie sprawę, że opis jest niepełny. Chcia­łem zwró­cić uwagę na ciekawsze/trudniejsze momenty. Propo­nuję dokład­nie prze­ana­li­zo­wać sobie całą klasę z linka poda­nego powyżej.

Tak jak obie­ca­łem, w następ­nym wpisie opiszę jak to wywo­ły­wać w możli­wie bezpiecz­nym środowisku.