Software Security
Software Security
Mathias Payer
1 Introduction 1
ii
Contents
5 Attack Vectors 50
5.1 Denial of Service (DoS) . . . . . . . . . . . . . 50
5.2 Information Leakage . . . . . . . . . . . . . . . 51
5.3 Confused Deputy . . . . . . . . . . . . . . . . . 52
5.4 Privilege Escalation . . . . . . . . . . . . . . . 54
5.4.1 Control-Flow Hijacking . . . . . . . . . 56
5.4.2 Code Injection . . . . . . . . . . . . . . 58
5.4.3 Code Reuse . . . . . . . . . . . . . . . 60
5.5 Summary . . . . . . . . . . . . . . . . . . . . . 61
6 Defense Strategies 62
6.1 Software Verification . . . . . . . . . . . . . . . 62
6.2 Language-based Security . . . . . . . . . . . . 63
6.3 Testing . . . . . . . . . . . . . . . . . . . . . . 64
6.3.1 Manual Testing . . . . . . . . . . . . . . 65
6.3.2 Sanitizers . . . . . . . . . . . . . . . . . 69
6.3.3 Fuzzing . . . . . . . . . . . . . . . . . . 72
6.3.4 Symbolic Execution . . . . . . . . . . . 81
6.4 Mitigations . . . . . . . . . . . . . . . . . . . . 85
6.4.1 Data Execution Prevention (DEP)/WˆX 86
6.4.2 Address Space Layout Randomization
(ASLR) . . . . . . . . . . . . . . . . . . 87
6.4.3 Stack integrity . . . . . . . . . . . . . . 91
iii
Contents
8 Appendix 123
8.1 Shellcode . . . . . . . . . . . . . . . . . . . . . 123
8.2 ROP Chains . . . . . . . . . . . . . . . . . . . 124
8.2.1 Going past ROP: Control-Flow Bending 124
8.2.2 Format String Vulnerabilities . . . . . . 125
9 Acknowledgements 126
References 127
iv
1 Introduction
1
1 Introduction
2
1 Introduction
3
1 Introduction
4
1 Introduction
5
2 Software and System
Security Principles
6
2 Software and System Security Principles
2.1 Authentication
7
2 Software and System Security Principles
create new passwords every few months, they are not allowed to
repeat passwords, and passwords must contain a set of unique
character types (e.g., upper case letters, lower case letters,
numbers, and special characters) to ensure sufficient entropy.
Current best practices are to allow users freedom in providing
sufficiently long passwords. It is easier to achieve good entropy
with longer passwords than having users forget their complex
short passwords.
Biometric logins may target fingerprints, Iris scans, or be-
havioral patterns (e.g., how you swipe across your screen).
Using biometric factors for authentication is convenient as
users cannot (generally) neither lose nor forget them. Their
key limitation is the lack of replay resistance. Different to pass-
words, biometrics cannot be changed, so a loss of data means
that this authentication form loses its utility. For example, if
someone’s fingerprints are known by the attacker, they can no
longer be used for authentication.
Property can be anything the user owns that can be presented
to the authentication system such as smartcards, smartphones,
or USB keys. These devices have some internal key generation
mechanism that can be verified. An advantage is that they are
easily replaceable. The key disadvantage is that they should
not be used by itself as, e.g., the smartphone may be stolen.
Instead of just using a single username and password pair,
many authentication systems nowadays rely on two or more
factors. For example, a user may have to log in with username,
password, and a code that is sent to their phone via text
message.
8
2 Software and System Security Principles
Access rights encode what entities a user has access to. For
example, a user may be allowed to execute certain programs but
not others. They may have access to their own files but not to
files owned by another user. The Unix philosophy introduced a
similar access right matrix consisting of user, group, and other
rights.
Each file has an associated user which may have read, write,
or execute rights. In addition to the user who is the primary
owner, there may be a group with corresponding read, write, or
execute rights, and all others that are not part of the group with
the same set of rights. A user may be member of an arbitrary
number of groups. The system administrator organizes group
membership and may create new users. Through privileged
services, users may update their password and other sensitive
data.
More information about access rights, access control (both
mandatory and discretionary) along with role based access
control can be found in many books on Usenix system design
or generally system security.
9
2 Software and System Security Principles
10
2 Software and System Security Principles
11
2 Software and System Security Principles
2.4 Isolation
12
2 Software and System Security Principles
13
2 Software and System Security Principles
14
2 Software and System Security Principles
15
2 Software and System Security Principles
2.6 Compartmentalization
16
2 Software and System Security Principles
17
2 Software and System Security Principles
18
2 Software and System Security Principles
19
2 Software and System Security Principles
20
2 Software and System Security Principles
of the current computation. Since then the term bug was used
for any unexpected computation or failure that was outside of
the specification of a system or program.
As a side note, while the term bug was coined by Grace Hopper,
the notion that computer programs can go wrong goes back to
Ada Lovelace’s notes on Charles Babbage’s analytical machine
where she noted that “an analysing process must equally have
been performed in order to furnish the Analytical Engine with
the necessary operative data; and that herein may also lie a
possible source of error. Granted that the actual mechanism is
unerring in its processes, the cards may give it wrong orders.”
A software bug is therefore a flaw in a computer program that
causes it to misbehave in an unintended way while a hardware
bug is a flaw in a computer system. Software bugs are due to
human mistake in the source code, compiler, or runtime system.
Bugs result in crashes and unintended program state. Software
bugs are triggered through specific input (e.g., console input,
21
2 Software and System Security Principles
2.9 Summary
22
2 Software and System Security Principles
23
3 Secure Software Life Cycle
24
3 Secure Software Life Cycle
25
3 Secure Software Life Cycle
26
3 Secure Software Life Cycle
27
3 Secure Software Life Cycle
28
3 Secure Software Life Cycle
29
3 Secure Software Life Cycle
30
3 Secure Software Life Cycle
3.6 Summary
31
4 Memory and Type Safety
32
4 Memory and Type Safety
33
4 Memory and Type Safety
34
4 Memory and Type Safety
35
4 Memory and Type Safety
36
4 Memory and Type Safety
37
4 Memory and Type Safety
1 char *c = (char*)malloc(24);
2 for (int i = 0; i < 26; ++i) {
3 // 1.) buffer overflow for i >= 24
4 c[i] = 'A' + i;
5 }
6 // 2.) violation through a direct write
7 c[26] = 'A';
8 c[-2] = 'Z';
9 // 3.) invalid pointers: OK if not dereferenced
10 char *d = c+26;
11 d -= 3;
12 *d = 'C';
38
4 Memory and Type Safety
39
4 Memory and Type Safety
1 char *c = malloc(26);
2 char *d = c;
3 free(d);
4 // violation as c no longer points to a valid
object
5 c[23] = 'A';
40
4 Memory and Type Safety
41
4 Memory and Type Safety
42
4 Memory and Type Safety
1 struct BankAccount {
2 char acctID[3]; int balance;
3 } b;
4 b.balance = 0;
5 char *id = &(b.acctID);
6 // Instrumentation: store bounds
7 lookup(&id)->bse = &(b.acctID);
8 lookup(&id)->bnd = &(b.acctID)+3;
9 // --
10 char *p = id; // local, remains in register
11 // Instrumentation: propagate information
12 char *p_bse = lookup(&id)->bse;
13 char *p_bnd = lookup(&id)->bnd;
14 // --
15 do {
16 char ch = readchar();
17 // Instrumentation: check bounds
18 check(p, p_bse, p_bnd);
19 // --
20 *p = ch;
21 p++;
22 } while (ch);
43
4 Memory and Type Safety
44
4 Memory and Type Safety
45
4 Memory and Type Safety
47
4 Memory and Type Safety
Base
Greeter Exec
4.4 Summary
Memory and type safety are the root cause of security vulnera-
bilities. Memory safety defines spatial and temporal capabilities
for pointers. Spatial memory safety guarantees that pointers
can only access objects in the corresponding bounds. Temporal
memory safety checks for liveness of the underlying object.
48
4 Memory and Type Safety
49
5 Attack Vectors
50
5 Attack Vectors
51
5 Attack Vectors
52
5 Attack Vectors
Figure 5.1: Barney Fife, the confused deputy, locks up half the
town due to a chain of misunderstandings. From
“The Andy Griffith Show”, 1961, public domain.
53
5 Attack Vectors
54
5 Attack Vectors
Integrity C *C D *D
Code Control-flow
Bad things corruption hijack
Data-only
55
5 Attack Vectors
56
5 Attack Vectors
1 int benign();
2
3 void vuln(char *attacker) {
4 int (*func)();
5 char buf[16];
6
7 // Function pointer is set to benign function
8 func = &benign;
9
10 // Buffer overflow may compromise memory safety
11 strcpy(buf, attacker);
12
13 // Attacker may hijack control-flow here.
14 func();
15 }
Listing 5.1: Buffer overflow into function pointer.
57
5 Attack Vectors
58
5 Attack Vectors
59
5 Attack Vectors
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int main(int argc, char* argv[]) {
6 char cookie[32];
7 printf("Give me a cookie (%p, %p)\n",
8 cookie, getenv("EGG"));
9 strcpy(cookie, argv[1]);
10 printf("Thanks for the %s\n", cookie);
11 return 0;
12 }
Listing 5.2: Stack based code injection.
60
5 Attack Vectors
5.5 Summary
61
6 Defense Strategies
62
6 Defense Strategies
63
6 Defense Strategies
6.3 Testing
64
6 Defense Strategies
65
6 Defense Strategies
code. Assertions help test negative test cases and find bugs
before they corrupt any state which would make them hard to
triage.
Unit tests are small test cases that focus on an individual unit
or feature of the program. The Google testing framework
[7] simplifies the implementation of such tests. To provide
necessary application state, unit testing frameworks enable a
wide range of mock operations.
Integration tests allow testing of interactions between individual
modules. For example, an integration test could measure the
interaction between a browser’s DOM and the printer daemon
that creates a visual representation of the DOM to send it off
to a printer.
System testing tests the full application. For example, a browser
displaying a web page and a user interacting with that page to
accomplish a certain task such as filling out a form and sending
it off.
Beta testing leverages a small set of users that thoroughly
test the software to find remaining flaws. Any identified bug
is triaged and fixed. It is good testing practice to create a
test case for each identified flaw to protect against regression.
Regression happens if a code change suddenly fails existing
test cases. Keeping test cases for each fixed bug allows early
detection if a bug is introduced again and may catch similar
bugs as well.
An interesting question is what metric is used to evaluate
the quality of a test suite. A deterministic metric allows an
absolute evaluation of the quality of the suite, i.e., how well
66
6 Defense Strategies
67
6 Defense Strategies
The branch edge from the check in the loop to the end of the
loop is never followed.
Branch coverage measures, for each branch, if it has been
followed. Again, coverage tracking can be done using a sim-
ple array and instrumentation that marks executed branches.
Branch coverage marks both the execution of the basic block
and the branch to a given basic block and is therefore a super
set of simple statement coverage. Full branch coverage implies
full statement coverage. Unfortunately, branch coverage may
not be precise enough:
1 int arr[5] = { 0, 1, 2, 3, 4 };
2 int func(int a, int b) {
3 int idx = 4;
4 if (a < 5) idx -= 4; else idx -= 1;
5 if (b < 5) idx -= 1; else idx += 1;
6 return arr[idx];
7 }
Listing 6.2: Limitation of branch coverage.
68
6 Defense Strategies
6.3.2 Sanitizers
69
6 Defense Strategies
cases, unit tests, or under fuzz testing, the sanitizers can de-
tect violations at the source of the flaw and not just when
the process traps. Sanitizers detect low level violations of,
e.g., memory safety or type safety, not high-level functional
properties.
Recently several new sanitizers were added to the LLVM com-
piler framework to target different kinds of vulnerabilities:
AddressSanitizer, LeakSanitizer, MemorySanitizer, Undefined-
BehaviorSanitizer, ThreadSanitizer, and HexType.
AddressSanitizer (ASan) [30] detects memory errors. It places
red zones around objects and checks those objects on trigger
events. The typical slowdown introduced by ASan is 2x. The
tool can detect the following types of bugs:
70
6 Defense Strategies
• Unsigned/misaligned pointers
• Signed integer overflow
• Conversion between floating point types leading to over-
flow
• Illegal use of NULL pointers
• Illegal pointer arithmetic
• and many more (check the documentation)
71
6 Defense Strategies
6.3.3 Fuzzing
72
6 Defense Strategies
Debug
Input Generation Exe Coverage
Tests
73
6 Defense Strategies
74
6 Defense Strategies
75
6 Defense Strategies
76
6 Defense Strategies
77
6 Defense Strategies
78
6 Defense Strategies
79
6 Defense Strategies
80
6 Defense Strategies
81
6 Defense Strategies
82
6 Defense Strategies
83
6 Defense Strategies
a0
x=-2
b0<5 b0<5
84
6 Defense Strategies
6.4 Mitigations
85
6 Defense Strategies
86
6 Defense Strategies
87
6 Defense Strategies
No defenses DEP
Memory Memory
0xfff RWX 0xfff RW-
stack stack
data data
88
6 Defense Strategies
89
6 Defense Strategies
No defenses ASLR
Memory Memory
0xfff RWX 0xf?? RWX
stack stack
data data
90
6 Defense Strategies
91
6 Defense Strategies
92
6 Defense Strategies
93
6 Defense Strategies
1 ; Prologue:
2 mov %fs:0x28,%rax
3 mov %rax,-0x8(%rbp)
4 xor %eax,%eax
5
6 ; Epilogue:
7 mov -0x8(%rbp),%rcx
8 xor %fs:0x28,%rcx
9 je <safe_return>
10 callq <__stack_chk_fail@plt>
11 safe_return:
12 leaveq
13 ret
Listing 6.4: Prologue and epilogue for stack canaries.
94
6 Defense Strategies
two stack frames for each function invocation: the regular stack
frame and the shadow stack frame. The two stack frames can
be of different size, i.e., the frame with the sensitive data may
be much smaller than the regular stack frame. Figure 6.4 shows
the shadow stack layout.
Old %RIP
Old %RIP Old %RSP
Old %RBP (if saved) Old %RIP
Caller data Old %RSP
Function parameter #n
... Stacks grow
Function parameter #2
Function parameter #1
Old %RIP
Old %RBP (if saved)
Local variable #1
Local variable #2
Saved register #1
Saved register #2
95
6 Defense Strategies
Safe stacks are a form of stack integrity that reduce the perfor-
mance penalty compared to shadow stacks. A shadow stack
always keeps two allocated stack frames for each function invo-
cation, resulting in overhead. Stack canaries are only added if
unsafe buffers are in a stack frame. The goal of safe stacks is to
achieve security guarantees of shadow stacks with low perfor-
mance overhead by only executing checks for unsafe objects.
All variables that are accessed in a safe way are allocated on
the safe stack. An optional unsafe stack frame contains all vari-
ables that may be accessed in an unsafe way. A compiler-based
analysis infers if unsafe pointer arithmetic is used on objects or
if references to local variables escape the current function. Any
objects that are only accessed in safe ways (i.e., no odd pointer
arithmetic and no reference to the object escapes the analysis
scope) remain on the safe stack frame. Unsafe stack frames are
only allocated when entering a function that contains unsafe
objects. This reduces the amount of unsafe stack frame allo-
cations, achieving low performance overhead while providing
equal security to a safe shadow stack implementation. Figure
6.5 shows the safe stack layout.
96
6 Defense Strategies
97
6 Defense Strategies
98
6 Defense Strategies
99
6 Defense Strategies
100
6 Defense Strategies
101
6 Defense Strategies
CHECK(fn);
(*fn)(x);
102
6 Defense Strategies
103
6 Defense Strategies
1 0xf000b400
2
3 int bar1(int b, int c, int d);
4
5 int bar2(char *str);
6
7 void bar3(char *str);
8
9 void B::foo(char *str);
10
11 class Greeter :: Base {... };
12 void Base::bar5(char *str);
13
14 void Greeter::sayHi(char *str);
15
16 class Related :: Greeter {... };
17 void Related::sayHi(char *str);
18
19 Greeter *o = new Greeter();
20 o->sayHi(char *str);
Listing 6.6: Example of precision trade-offs for different CFI
policies.
In the example above, let us look at the sayHi call in the last
line. The valid function policy would allow all functions except
the raw address 0xf000b400 which points somewhere into
the code area (but not to a valid function). The arity policy
would allow the set of bar2, bar3, foo, Base::bar5,
Greater::sayHi , Related::sayHi. The function prototype
104
6 Defense Strategies
105
6 Defense Strategies
106
6 Defense Strategies
107
6 Defense Strategies
6.5 Summary
108
7 Case Studies
Web security is a broad topic that would deserve its own book
due to the many aspects that need to be secured. In this Section,
we will look at three broad aspects of web security: protecting
the server, protecting the interaction between server and client,
and protecting the web browser. Web servers (and browsers)
are long running software applications that are exposed to
adversaries as they serve external requests. Generally, software
109
7 Case Studies
110
7 Case Studies
111
7 Case Studies
112
7 Case Studies
Network Local
qmail-smtpd qmail-inject
(qmaild) (“user”)
qmail-queue
(suid qmailq)
qmail-send
(qmails)
qmail-rspawn qmail-lspawn
(qmailr) (root)
qmail-remote qmail-local
(qmailr) (sets uid user)
113
7 Case Studies
114
7 Case Studies
1 <html><head><title>Display a file</title></head>
2 <body>
3 <? echo system("cat ".$_GET['file']); ?>
4 </body></html>
The PHP script above leverages the simple cat utility to return
the contents of a user supplied file back to the user. Unfortu-
nately, system executes a full shell, resulting in powerful com-
mand injection vulnerabilities. The arguments to system are
the string cat concatenated with the user-supplied value in the
parameter file, e.g., through http://web.site/?file=user.
For example ; allows chaining two commands such as
http://web.site/?file=user%3Bcat%20%2fetc%2fpasswd
to leak /etc/passwd. Simply blocking ; is not enough, the
user supplied data in file is untrusted and must be pruned
either through validation (comparing against a set of allowed
values) or escaping where the user-supplied values are clearly
marked as string, e.g., resulting in system("cat 'file; cat
/etc/passwd'") which would result in a file not found error.
Note that you should not write your own escape functions,
each web framework has their own escape functions that allow
for different contexts. Even better, instead of leveraging
system, simply open and read the file instead of launching a
set of sub processes.
115
7 Case Studies
116
7 Case Studies
Cross Site Scripting (or XSS) exploits trust user has in a web
site. XSS enables an adversary to inject and execute JavaScript
(or other content) in the context of another web page. For exam-
ple, malicious JavaScript code may be injected into a banking
web page to execute on behalf of a user that is logged into her
bank account. This allows the adversary to extract username
and password or to issue counterfeit transactions. There are
three different kinds of XSS: persistent/stored, reflected, and
client-side XSS.
Persistent XSS modifies data stored on the server to include
JavaScript code. The adversary interacts with the web appli-
cation, storing the code on the server side. When the user
interacts with the web application, the server responds with a
page that includes the attacker-supplied code. An example of
persistent XSS is a simple chat application where the adversary
includes <script>alert('Hi there');</script> in the chat
message. This message is stored in a database on the server.
When the message is sent to the user, the JavaScript code is
executed on behalf of the user’s browser in the user’s session.
Persistent XSS is enabled through a lack of input sanitization
on the server side. Common locations of such errors are feed-
back forms, blog comments, or even product meta data (you
do not have to see the response to execute it). In this scenario,
the user only has to visit the compromised website.
Reflected XSS encodes the information as part of the request
which is then reflected through the server back to the user.
Instead of storing the JavaScript code on the server side, it
117
7 Case Studies
is encoded as part of the link that the user clicks on. A web
interface may return the query as part of the results, e.g.,
“Your search for ‘query’ returned 23 results.”. If the query is
not sanitized, then JavaScript code will be executed on the
user side. The code is encoded as part of the link and the user
is tricked to click on the prepared link. The bug for this type
of XSS is on the server side as well.
Client-side XSS targets lack of sanitization on the client side.
Large web applications contain a lot of JavaScript code that
also parses input data from, e.g., AJAX/JSON requests or
even input that is passed through the server. This JavaScript
code may contain bugs and missing sanitization that allows
the adversary to execute injected JavaScript in the context of
the web application as the user. Similarly to reflected XSS,
the user must follow a compromised link. The server does not
embed the JavaScript code into the page through server-side
processing but the user-side JavaScript parses the parameters
and misses the injected code. The bug is on the client side, in
the server-provided JavaScript.
118
7 Case Studies
119
7 Case Studies
120
7 Case Studies
121
7 Case Studies
122
8 Appendix
8.1 Shellcode
123
8 Appendix
124
8 Appendix
125
9 Acknowledgements
126
References
127
9 Acknowledgements
128
9 Acknowledgements
129
9 Acknowledgements
130
9 Acknowledgements
131
9 Acknowledgements
132