Hoppa yfir í efnið

XML og ytri einindir

XML er textasnið sem lýsir gögnum með stigskiptum einindum, eigindum og hreiðrun. Það telst talsvert eldra og orðfleira en JSON-sniðið, en á það engu að síður til að skjóta upp kollinum við gagnaflutninga, í margvíslegum skjölum, SOAP-þjónustum og í ýmsum gerðum skráarsniða.

Inni í XML-skjölum getur auk þess verið að finna sérstakar skilgreiningar á einindum. Heimili þjónninn ótraustu XML-skjali að vísa í ytri skrár eða vefslóðir, getur svo farið að sjálfur þáttarinn (e. parser) taki sig til og sæki innihaldið fyrir hönd notandans. Það kallast XXE, XML external entity.

Verkefnið Vörulistinn tekur við sérsmíðuðu XML-skjali og birtir í kjölfarið þau vöruheiti sem innflutt voru. Þar er okkur veitt nákvæm slóð á skaðlausa prófskrá, ásamt tiltekinni flaggskrá sem hvílir inni í lokuðum sandkassa. Við þurfum því hvorki að grennslast fyrir um sjálft skráakerfið né heldur varpa af stað netbeiðnum út í bláinn.

Aðeins tilgreindar labbskrár

Notaðu aðeins XML-leiðina og skráarslóðirnar sem verkefnið birtir. Ekki reyna að lesa kerfisskrár, frumkóða, lykla eða skrár annarra þjónusta. Ekki búa til endurkvæmar einindir eða önnur XML-gögn sem geta valdið auðlindaþroti.

XML frá grunni

XML, Extensible Markup Language, notar merki til að lýsa gögnum:

<?xml version="1.0" encoding="UTF-8"?>
<product id="17">
  <name>Lopapeysa</name>
  <price currency="ISK">12900</price>
</product>

product, name og price eru stök (e. elements) XML-skjalsins. id og currency eru eigindir (e. attributes). Sérhverju skjali er gert að hafa eitt rótstak, sem í þessu tilfelli er product, og mikilvægt er að gæta þess að upphafs- og lokamerki rími saman.

XML-skjal telst vera vel formað (e. well-formed) sé þess gætt að grunnreglur setningafræðinnar séu uppfylltar í hvívetna. Þetta skjal er til dæmis gallað:

<product>
  <name>Lopapeysa</product>

name var ekki lokað áður en product lokaði. XML-þáttari (e. XML parser) les textann, athugar slíkar reglur og býr til gagnaskipan sem forritið vinnur með.

Vefleið gæti tekið við XML svona:

POST /admin/import HTTP/1.1
Host: nordurmarkadur.example
Content-Type: application/xml

<products>
  <product><name>Lopapeysa</name></product>
</products>

Hausinn Content-Type kemur þjóninum að góðum notum við valið á réttum þáttara. Ef leiðin býst við JSON getur XML-líkami fengið 415 Unsupported Media Type áður en nokkur XML-túlkun fer fram.

DTD lýsir reglum og einindum

Með DTD-skilgreiningum (e. Document Type Definition) er unnt að mæla fyrir um leyfilega uppbyggingu XML-skjala auk þess að skilgreina þar tilgerðar einindir. Innri DTD kemur fyrir inni í skjalinu:

<?xml version="1.0"?>
<!DOCTYPE products [
  <!ENTITY greeting "Halló úr einind">
]>
<products>
  <product>
    <name>&greeting;</name>
  </product>
</products>

<!ENTITY greeting "..."> skilgreinir XML-einind (e. XML entity). Í stað tilvísunarinnar &greeting; kemur sjálfur textinn um leið og þáttarinn hefst handa við að vinna úr einindunum.

Birtist kveðjan „Halló úr einind“ í svari innflutningsins, höfum við af því þá vitneskju að þáttarinn hafi heimilað DTD og rækilega unnið úr hinni innri einind. Þetta er skaðlaust, gagnlegt grunnpróf áður en ytri skrá er nefnd.

Einind er ekki HTML-eining

XML notar einnig innbyggðar tilvísanir eins og &lt; fyrir < og &amp; fyrir &. Sérskilgreind DTD-einind myndar því nýtt lag sem þáttaranum er í lófa lagið að þenja út jafnhliða lestri skjalsins.

Ytri einind

Svo eru það hinar ytri einindir, en þær sækja gögn sín til auðkenndra utanaðkomandi uppspretta í stað þess að bera textann beint með sér í DTD-hlutanum:

<!DOCTYPE products [
  <!ENTITY lab_note SYSTEM "file:///tmp/challenge/proof.txt">
]>

Gildið SYSTEM telst til kerfisauðkennis sem þáttaranum er gert að túlka sem slóð. Sé aðgangur að skrám heimilaður, getur tilvísunin &lab_note; hæglega umbreyst í sjálft innihald prófskrárinnar.

Fullt, afmarkað próf gæti verið:

<?xml version="1.0"?>
<!DOCTYPE products [
  <!ENTITY lab_note SYSTEM "file:///tmp/challenge/proof.txt">
]>
<products>
  <product><name>&lab_note;</name></product>
</products>

Slóðin hér er aðeins dæmi um tilbúna skrá inni í sandkassa. Lifandi verkefnið birtir sína eigin labbslóð; notaðu hana óbreytta.

Flæðið verður:

XML-beiðni
   ↓
XML-þáttari sér ytri einind
   ↓
þáttari les tilgreinda labbskrá
   ↓
innihaldið lendir í vöruheiti
   ↓
vefþjónn birtir heitið í svari

Nái þessi hegðun að teygja sig til óheimilaðra ytri eininda, þá stöndum við frammi fyrir XXE-veikleikanum. Í verkefninu er lestrargetan afmörkuð með jail og aðeins tvær tilbúnar skrár eru markmið.

Innri einind fyrst, ytri einind næst

Vönduð rannsóknarvinna einkennist af því að breyta einungis einu atriði í senn:

  1. Sendu gilt XML án DTD og varðveittu svarið.
  2. Bættu við innri einind með einstöku prófmerki.
  3. Skiptu aðeins uppsprettu einindarinnar yfir í tilgreinda prófskrá.
  4. Notaðu loks flaggskrána sem verkefnið gefur upp.

Ef skref 2 mistekst er engin ástæða til að prófa ytri skrá strax. Ef innri einind virkar en ytri fær sérstaka höfnun hefur þáttari líklega ólíkar reglur fyrir ytri aðgang.

Hyggilegast er að vista XML-kóðann í skrá, svo varna megi því að skelin taki upp á því að frukka eitthvað í táknum á borð við &, < eða sjálfum gæsalöppunum:

curl -i -X POST "$BASE_URL/admin/import" \
  -H 'Content-Type: application/xml' \
  --data-binary @payload.xml

Valkosturinn --data-binary stendur vörð um línuskipti og bæti mun betur en ef langt og flókið XML væri hreinlega skrifað beint inn í sjálfa skipanalínuna.

Skráarlestur og þjónsbeiðni eru tvær mögulegar leiðir

Kerfisauðkenni ytri einindar á það til, í sumum þátturum, að vísa rakleiðis í staðbundna skrá. Ef þáttari hefur netaðgang getur það einnig valdið beiðni frá þjóninum og líkist þá SSRF.

file-samhengi → mögulegur skráarlestur
HTTP-samhengi → möguleg beiðni frá þjóninum

Hvort tveggja ræðst af þáttara, stillingum og leyfum ferlisins. Sú staðreynd að innri einind nái að þenjast út er þó fjarri því að vera fullgild sönnun þess að hvort heldur skráar- eða netaðgangur sé heimilaður.

Vörulistinn leyfir aðeins afmarkaðan lestur tilbúinna skráa og hefur enga útleið. Við prófum því ekki HTTP-slóðir, innri þjónustur eða ytri móttakara í þessum kafla.

Hvað segja villurnar?

Villuboðin geta oft verið einkar upplýsandi um það til hvaða skrefs þáttaranum tókst raunverulega að komast:

Villa eða hegðun Hagnýt túlkun
„DOCTYPE is disallowed“ DTD var stöðvað áður en einindir voru unnar
„Entity is not defined“ Tilvísun fannst en skilgreiningin var ekki virk eða rétt
Prófunarmerki birtist Innri einind var þanin út
„Failed to load external entity“ Ytri einind var túlkuð en uppsprettan fannst ekki eða var ekki leyfð
&name; birtist bókstaflega Forritið gæti hafa sleppt einindaúrvinnslu eða unnið XML sem texta

Stök villa jafngildir engan veginn staðfestum skráarlestri. Prófunarmerki, sem sprettur upp úr þekktri labbskrá, hlýtur ætíð að teljast til langtum sterkari sönnunargagna. Gættu þess að bera ætíð saman gilt grunnskjal, innri einind og loks eina og heimilaða ytri skrá.

Ef þjónninn skilar almennri 400-villu skaltu fyrst staðfesta að rótstak, reitanöfn og Content-Type passi við venjulegu beiðnina. Þess er vert að minnast að XXE-prófun í röngu XML-skema nær ef til vill aldrei í tæri við þann hluta kóðans sem sér um að birta sjálfa niðurstöðuna.

Kóðavísbendingar í þáttara

Í Python með lxml getur verkefni viljandi búið til þáttara sem vinnur úr ytri einindum:

parser = etree.XMLParser(
    load_dtd=True,
    resolve_entities=True,
    no_network=True,
)
root = etree.fromstring(request.data, parser)

load_dtd=True og resolve_entities=True eru sterkar vísbendingar um að DTD og einindir séu virkar. Stillingin no_network=True mælir vissulega gegn því að HTTP-beiðnir séu sendar, en hún gæti engu að síður heimilað afmarkaðan skráarlestur inni í sjálfu kerfinu, allt eftir því hvernig umhverfið er úr garði gert. Það er nákvæmlega samhengi Vörulistans.

Aðrar vísbendingar geta verið skýr villa um bannað DOCTYPE, þáttari sem notar einfaldað XML-safn án DTD-stuðnings eða frumkóði sem setur resolve_entities=False. Þá er einföld ytri einind ólíkleg og nemandinn ætti ekki að senda sífellt flóknari DTD.

Heiti bókasafnsins eitt og sér segir okkur fjarri því alla söguna. Sama lxml-safnið kann nefnilega að vera stillt á fjölmarga vegu, og ekki má gleyma því að ólíkar útgáfur geta átt það til að hnekkja sjálfgefnum gildum. Hegðun sjálfs grunnprófsins og sýnilegar stillingar hljóta því ætíð að vega þyngra á metunum.

Blind XXE sem hliðarspor

Í blindu XXE-tilviki birtir forritið ekki útþanda einind í svari. Lokað CTF getur þá útvegað sérstakt innra logg sem staðfestir eina tilbúna beiðni eða sýnir að einind var sótt.

Við förum ekki yfir utanbands-DTD, gagnasendingu til þriðja aðila eða aðferðir til að lesa almennar skrár án svars. Slíkt eykur bæði flækjustig og áhættu og er ekki nauðsynlegt til að skilja grunnatriðið: XML-þáttari getur unnið úr tilvísun sem kemur úr ótraustu skjali.

Ef ekkert svar, engin villa og ekkert verkefnisútvegað logg breytist hefur tilgátan enga mælingu. Ekki gera handahófskenndar ytri beiðnir til að „sjá hvort eitthvað gerist“.

Einindaþensla og auðlindaþrot

XML-einindir búa yfir þeim eiginleika að geta vísað hver í aðra og gert lítið og krúttlegt skjal óheyrilega umfangsmikið á svipstundu. Slík ofsafengin einindaþensla gengur gjarnan undir nafninu „milljarðshláturs-árás“ (e. Billion Laughs) og getur hún auðveldlega gengið nærri vinnsluminni og vinnslutíma kerfisins.

Við smíðum ekki slíkt skjal. Fyrirbærið er neitun á þjónustu, ekki nauðsynlegt próf fyrir XXE-skráarlestur. Verkefnið okkar styðst við fáar og knappar einindir, auk þess sem það vísar stórum skjölum rakleiðis á bug.

Ef CTF-þjónn hægir á sér eftir XML-tilraun skaltu stöðva og tilkynna vandann. Ekki endurtaka beiðnina eða auka fjölda eininda.

PHP-síur eru ekki XML-eiginleiki

Í slóðaflakkskaflanum sáum við php://filter sem PHP-sértækan straumhjúp. Hann tilheyrir PHP-skráaraðgangi, ekki XML eða DTD sjálfu.

Ef PHP-byggður XML-þáttari leyfir kerfisauðkenni sem PHP vinnur úr getur straumhjúpur stundum komið við sögu í CTF. Það er samspil tveggja tækniþátta, ekki grunnlausn XXE. Vörulistinn notar Python og lxml, svo PHP-síur eru skýr vísbending gegn því og utan umfangs.

Göngudæmi: Vörulistinn

Byrjaðu á því að sækja þér sýnishorn af hefðbundnu XML-skjali úr verkefninu og sendu það af stað með öllu óbreyttu. Varðveittu beiðnina í Repeater eða sem payload.xml. Bættu síðan við innri einind með einstöku textamerki og settu tilvísunina í name-eininguna. Staðfestu að merkið birtist í innflutningssvarinu.

Verkefnið mun ljóstra upp um slóðina að sérstakri proof-skrá sem hvílir inni í sandkassanum. Láttu þér nægja að eiga við einindarskilgreininguna sjálfa þannig að hún styðjist við þessa fullbúnu slóð. Þegar þekkta prófmerkið birtist hefurðu staðfest ytri skráarlestur án þess að kanna aðrar skrár.

Að lokum skaltu skipta prófslóðinni út fyrir nákvæmu flaggslóðina sem verkefnið birtir:

<!DOCTYPE products [
  <!ENTITY lab_value SYSTEM "<flaggslóð úr verkefninu>">
]>
<products>
  <product><name>&lab_value;</name></product>
</products>

Sendu skjalið með --data-binary. Ef fáninn birtist í vöruheitinu er verkefninu lokið. Kaflinn birtir hvorki raunverulega flaggslóð lifandi þjónustu né fánann.

Ef prófskrá virkar en flaggskrá ekki skaltu bera strengina saman nákvæmlega. Ekki skipta yfir í kerfisskrár, neteinindir eða PHP-síur. Verkefnið á að veita allar nauðsynlegar slóðir og villan gæti verið einföld stafsetning eða XML-kóðun.

Vísbendingar gegn veikleikum

DOCTYPE sem er hafnað áður en innri einind er unnin mælir gegn einföldu XXE. Innri einind sem birtist en ytri aðgangur fær stöðuga, skýra höfnun bendir til að ytri einindir séu lokaðar. JSON-endapunktur sem hafnar application/xml nær ekki XML-þættinum yfirleitt.

Að sjá XML í svarinu sannar heldur ekkert um þáttara þjónsins; vefurinn gæti aðeins vistað eða endurvarpað textann. Sterk staðfesting er að innri einind breytist í sitt skilgreinda gildi og að tilgreind labbskrá birtist síðan á sama stað.

Ef parser-stillingar sýna DTD óvirkt, ytri resolver fjarlægðan og netaðgang lokaðan er ekki skynsamlegt að prófa langan lista af einindaframsetningum. Færðu þig að annarri tilgátu eða öðru gagnasniði sem verkefnið bendir á.

Samantekt

XML-þáttari breytir textaskjali í gagnaskipan. DTD getur skilgreint innri einindir, og ytri einind getur vísað í aðra uppsprettu. XXE kemur upp þegar ótraust XML fær þáttara til að lesa skrá eða senda beiðni sem notandinn ætti ekki að stjórna.

Við byrjum á gildu XML, staðfestum innri einindaþenslu með skaðlausu merki og notum síðan aðeins tilbúna prófskrá. Villur eins og bannað DOCTYPE, óskilgreind einind eða misheppnuð ytri hleðsla segja hvar túlkun stöðvaðist. Blind utanbandsvinnsla og auðlindaþrotspróf eru utan þessa byrjendakafla.

Í næsta kafla skoðum við aðra leið sem gögn geta orðið að forritshlutum: afraðgreiningu. Þar getur vandinn falist í því að gagnasnið endursmíðar ekki aðeins gildi heldur hluti með hegðun.