Designing Security Architecture Solutions phần 4 pptx

48 326 0
Designing Security Architecture Solutions phần 4 pptx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

function requires relinking the code, which might not be an option. Incidentally, FreeBSD is the core for the new Mac X OS kernel, which also comes with open-source versions of secure shell (using OpenSSH) and SSL support (using OpenSSL). This situa- tion might mean that Apple’s new OS represents a leap in PC security and reliability (or maybe not). Sentinel There are several compile-time solutions to stack overflow problems. StackGuard implements a Sentinel-based overflow protection solution. StackGuard uses a compiler extension that adds stack-bounds checks to the generated code. Function calls, in code compiled with StackGuard, are modified to first insert a sentinel word called a canary onto the stack before the return address. All exploits that use sequential writing of bytes to write down the user stack until the return address is overrun must first cross the canary value. The canary is chosen at random to prevent attacks that guess the canary. Before the function returns, the canary is checked — and if modified, the pro- gram terminates. StackGuard will not protect against attacks that can skip over the canary word. Such exploits are believed to be very difficult to construct. Layer Layers are used to separate concerns. The user stack is essentially a collection of verti- cally layered stack frames, each containing two kinds of elements. ■■ Local variables and parameters that will change during the stack frame’s life. ■■ The frame pointers and return addresses, which should not change. Layers can be implemented at a fine level, separating data elements within the stack frame. Separating the data elements within the stack frame based on this division cre- ates a fine-level separation of concerns. Consider Figure 5.4, which shows this layered solution to buffer overflows. One solution is to reserve the stack for return addresses only and force all variables to be dynamically allocated. This solution has performance problems and is infeasible in cases where the source is unavailable. Another solution to buffer overflows is to use multiple stacks. This solution creates a data stack and an address stack, then separates the horizontal layers within each stack frame and moves the elements to the appropriate stack. The address stack is not modi- fiable by data overflows and cannot be set programmatically. The return address cannot be modified by the program through overruns and is controlled by the kernel. This solu- tion requires significant re-architecture of the OS and compilation tools (and is inap- plicable in most circumstances). Multiple stacks are possible within tools that execute as a single process on the host and provide their own run-time environment. Perl’s run-time environment uses multiple stacks. There are separate stacks for function call parameters, local variables, tempo- rary variables created during execution, return addresses (which are pointers to the next opcode), and scope-related current block execution information (execution jumps Code Review 115 Local Variables Local Variables Local Variables Local Variables Local Variables Local Variables Local Variables Local Variables Local variable stack Return Address Return Address Return Address Return Address SFP and return address pointer stack OR Heap Return Address Local Variables Return Address Local Variables Return Address Local Variables Return Address Local Variables Current stack Figure 5.4 Splitting the stack. after a next, last, or redo statement in a block). Perl also performs memory expansion by automatically resizing arrays. The underlying C libraries have been scrubbed of all known unsafe calls that might cause buffer overflows. If an application uses embedded Perl, however, where the interpreter is incorporated into a wrapper written in C code that uses unsafe library calls, it might still have buffer overflows caused by long strings returned by the Perl interpreter. The references contain links to more information about embedded Perl. Sandbox Layers can be implemented at a coarse level of granularity by isolating the entire appli- cation from the underlying machine. Recall the Sandbox pattern, a special case of the Layer pattern, used for abstraction. The sandbox separates the program from its execu- tion environment. Using this pattern, another solution to the buffer overflow exploit works by dropping the entire program into a sandbox. Goldberg, Wagner, Thomas, and Brewer, in [GWTB96], present a sandbox for Solaris called Janus. When a program is run inside the sandbox, Janus controls access to all system calls made by that program based on security policy modules. Modules, which are sets of access control rules, can be specified in configuration files. There is no mod- ification to the program, but there is an associated performance cost with intercepting all calls (Figure 5.5). The sandbox restricts access privileges absolutely; therefore, it might not be feasible to run certain daemons within Janus. Buffer overflows are con- tained because the sandbox, under the correct policy module, can prevent an execution context switch from succeeding. Wrapper If modification of the program is not an option, we can place the entire executable within a wrapper. Rogers in [Rog98] suggests fixing a buffer overflow problem in some LOW-LEVEL ARCHITECTURE 116 Operating system Janus Sandbox Program executable Policy module Figure 5.5 The sandbox. versions of rlogin by placing the executable within a wrapper. The overflow occurs in the TERM variable, used to set the user’s remote terminal description from the local ter- minal description. The wrapper either truncates the input string to the correct length or replaces a malformed input string with a correct default value. The wrapper then invokes rlogin with safe arguments (Figure 5.6.[a]). Libverify is a solution based on the wrapper pattern developed by Baratloo, Singh, and Tsai [BST00] and is provided as a dynamically loaded library (Figure 5.6[b]). Libverify does not require source code, unlike StackGuard, and can prevent overflows in com- piled executables. The solution is somewhat unusual, however, because it rewrites and relocates the instructions of the original program. The libverify library, at link time, rewrites the entire program so that each function call is preceded with a wrapper_ entry call and succeeded by a wrapper_exit call within the library. In their implementa- tion, each function is relocated to the heap because the Intel architecture does not allow enough space within the text segment of the process. The entry function stores the correct return address on a canary stack. The exit function verifies the return address of the function against the stored canary value (Figure 5.6[b]). The canary value is stored on a canary stack, also allocated on the heap. If the canary is modified, the wrapper_exit procedure terminates the program. Code Review 117 Program executable Store canary on entry function call within program or standard C library Check canary on exit (b) Function call Return User invocation Check Arguments Buggy executable Check return status (a) Call actual executable Return Figure 5.6 Wrapper. Interceptors Interceptors can catch some overflows at run time by making the stack non-executable or by redirecting function calls on the fly. Many buffer overflow exploits embed the opcodes for the shell code vector directly on the execution stack and then transfer control to the vector by redirecting the instruc- tion pointer to the stack. On some platforms — for example, Solaris 2.6 and above — we can prevent this situation by making the stack non-executable. In Solaris, we can set the noexec_user_stack in /etc/system to 1. By default, this value is set to 0 to be compliant with the Sparc Application Binary Interface (ABI) and Intel ABI. Any program that attempts to execute code on the stack will receive a SIGSEGV signal and will most likely core dump. A message will be logged by syslogd to the console and to /var/adm/messages. Setting the noexec_user_stack_log variable to 0 turns this log- ging behavior off. Intel chips do not provide hardware support for making stack segments non- executable. By default, it is not possible to designate the user stack non-executable on Linux, but there is a kernel patch that is not part of the standard Linux kernel from Solar Designer (www.openwall.com) that enables the administrator to make the user stack non-executable. Linus Torvalds resists including non-executable stacks as a kernel option on Linux because other overflow attacks do not depend on executing code on the stack. Intel chips permit the transfer of control to system calls at addresses not on the stack segment, using only arguments from the stack. Making the stack non-executable does nothing to prevent such an overflow exploit from succeeding and adds a perfor- mance penalty to the kernel. Some applications require an executable stack. For example, the Linux kernel makes legitimate use of an executable stack for handling trampolines. Although some UNIX sys- tems make legitimate use of an executable stack, most programs do not need this feature. Another alternative, developed again by Baratloo, Singh, and Tsai [BST00] from Bell Labs, works in situations where we do not have access to source code. Their solution for Linux, provided through a dynamically loaded library called libsafe, intercepts all unsafe func- tion calls and reroutes them to a version that implements the original functionality but does not allow any buffer overflow to overwrite the return address field or exit the stack frame. This situation is possible at run time, where we can compare the target buffer that might overflow to the stack frame pointer of the stack frame it is contained in and decide the maximum size that the buffer can grow to without smashing the return address. Lib- safe calls the standard C function if the boundary test is not violated (Figure 5.7). Inter- cepting all calls to library functions known to be vulnerable prevents stack smashing. Why Are So Many Patterns Applicable? Why are there so many different ways of solving buffer overflow exploits? Simply put, there are so many solutions to the buffer overflow problem because so many solutions LOW-LEVEL ARCHITECTURE 118 text Program executable Standard C library Safe C library Checks frame boundary violation System call Figure 5.7 Interceptor. work. Every point in program evolution, from static source to running executable, pre- sents its own opportunity to add overflow checks. Overflow attacks work at a fundamental level, exploiting the stored-program concept that allows us to treat data and instructions alike. We mix instruction executions (“Branch here,” “Load from there,” “Use this value to set the instruction pointer,” and so on) with program data (“Store this array of characters.”). We allow location proximity between data and instructions because we need speed within a processor. This proxim- ity has led to an assumed safety property: if we compile and debug our programs cor- rectly, the flow of control in the assembly code generated will follow the flow of control of the source code. We assume that we will always load the instruction pointer from a trusted location in the address space, and we assume that return addresses on the stack will be written once on entry and read once on exit. Buffer overflow exploits violate this desired safety property. Stack Growth Redirection Changing the direction that the stack grows in so that buffers grow away from the return address on a stack frame and not toward it will prevent all currently known buffer over- flow attacks. This action is infeasible, however, because the stack growth feature of the process segment is built into too many operating systems, hardware platforms, and tools. Buffer overflows are still feasible even if the stack growth is reversed; they are just harder to build. The exploits work by overflowing a buffer in the calling routine’s stack frame, not the current stack frame, by using a stolen reference to a buffer within the parent stack frame. If we can overflow this buffer, writing upward until the return address on the current stack frame is reached, we can overwrite the return value. No known exploits work this way, but the mechanism has been documented. Most solu- tions for buffer flow attacks with no or small changes can prevent this variation. Code Review 119 Hardware Support Hardware support for ensuring the safety property of instruction pointers can help. ■■ We can trap overflows in hardware. ■■ We can flag return addresses and throw exceptions if they are modified before the call returns. ■■ We can throw exceptions if the instruction pointer is loaded with an address that is out of some allowed set. ■■ We can protect frame boundaries from being overwritten. Indeed, although none of the patterns described previously use cryptographic tech- niques to block overflows, we could envision schemes that do so in hardware. Programs can have cryptographic keys that describe allowed context switches to block attempts to run /bin/sh/. Hardware modifications are unlikely to happen in the near future. Until we have hardware support, we must actively pursue the other alternatives that we have described in the preceding sections. Security and Perl Larry Wall’s Perl programming language has been called the “duct tape of the Internet.” It provides a powerful and flexible environment for prototyping, systems administra- tion, maintenance, and data manipulation and is excellent for building one-time solu- tions for complicated problems that would otherwise require too much effort when using C. There are several million Perl programmers and many excellent references on Perl usage (for example, [SC97], [WCO00], and [Sri98]). The Perl interpreter consists of a translator and an opcode executor. The translator converts a Perl script into a syntax tree of abstract opcodes. Each opcode corresponds to a call to a carefully optimized C function. The current stable version of Perl, release 5.6.1, has more than 350 opcodes. The Perl opcode generator then scans through the completed syntax tree, performing local optimizations to collapse subtrees or to pre- compute values known at compile time. Each opcode stores the next opcode to be potentially executed, imposing an execution sequence on the syntax tree. At run time, the executor follows a path through the syntax tree, running opcodes as it goes. Each opcode returns the next opcode to be run, which can be identical to the op_next links within each node (except in cases where branch statements and indirect function calls redirect traversal of the syntax tree). Perl is a full-fledged programming language, and the user can implement any security mechanism desired in code. There are several reasons for Perl’s undeserved reputation as a security risk. Perl and CGI. Perl powers many of the cgi-bin scripts used on Web servers. Poor argument validation, along with root-owned SUID Perl scripts, allowed many exploits based on carefully constructed URLs to succeed. Some exploits confused the issue, blaming the Perl language because of Perl code that implemented security LOW-LEVEL ARCHITECTURE 120 TEAMFLY Team-Fly ® poorly. Examples included Perl code that depended on hidden variables (which are visible in the source html), values in the HTTP header (which can be spoofed), or code that contained nonPerl-related Web security problems. Perl and system calls. SUID Perl scripts that do not validate command-line arguments or check environment variables and yet invoke a shell are susceptible (this statement applies to any program). Malicious users could insert input field separator (IFS) characters, such as pipes or semicolons, into arguments or could modify the PATH variable or change the current working directory in ways that give them a root shell. Bugs in old versions of Perl. Many of the exploits that worked on bugs in earlier versions have been fixed in current stable releases. Home-grown libraries. Perl provides support for almost all Internet protocols. Users who rolled their own, with all the attendant risks, blamed Perl for security holes. Poor coding practices. Almost anything goes in Perl (that includes almost anything dangerous). Execution of untrusted scripts. Once a host has been compromised, the presence of a powerful interpreter such as Perl makes automated attacks easier. This situation is not a fault of Perl’s but does highlight the risks of putting a powerful tool on a production box, where it can be exploited. HP machines configured in trusted computing mode forbid the use of compilers. This feature is useful for protecting production machines, because you should not be building software on a production box anyway. Unlike compiled code, which no longer needs the compiler, the Perl interpreter must be present for Perl scripts to execute. We will describe three patterns that Perl uses for supporting secure programming. Syntax Validation Perl has few rivals for manipulating text for complex pattern matching through regular expressions. You can write a lexical analyzer for most simple applications in one line. Command-line argument validation in Perl uses regular expressions whose syntax, for example, supports the following items: ■■ Single character matches. /[aeiouAEIOU]/ matches the vowels. ■■ Predefined character matches. \d matches a single digit, \w matches a single legal Perl name, \W matches any input that is not a legal Perl name, and so on. ■■ Sequencing. /tom/ matches tom exactly. ■■ Multipliers. /A+/ matches a string of one or more As. /A*/ matches a string of zero or more As. ■■ Alternation. /(a|b|c)/ matches any one of a, b, or c. Perl also has many other features for pattern matching, such as memory placeholders, anchors, substitutions, and pattern redirection. All of these features make it easy to check the syntax of inputs. We can create an alpha- bet of characters, along with a syntax tree for all valid inputs defined by an application. Code Review 121 Then, we can strip any input string of any invalid characters and subject the remainder to valid syntax checks before we use the input string as a username, a file to be opened, a command to be executed, or a URL to be visited. Sentinel Perl uses the sentinel pattern to mark any data value within the program as trusted, by virtue of being created inside the program, or untrusted, by virtue of being input into the program from the outside world. The tainted property is a sentinel. Marking data as tainted prevents the interpreter from using the data in an unsafe manner by detecting some actions that attempt to affect the external environment of the Perl script and mak- ing them illegal at run time. By default, any SUID or SGID Perl script is executed in taint mode. Alternatively, Perl allows taint checking on any script by invoking the Perl script with the -T switch, which marks all data input from outside the program as tainted. Perl marks variables that derive their values from tainted variables as also tainted. Examples of possibly tainted external values include data from a POST form to a cgi-bin script, command-line argu- ments, environment variables not set internally but inherited from the script’s parent, and file handles. Maintaining these markers on the flow of data through the program has a performance cost. This situation, however, enables the interpreter (which assumes that the code is trusted but the data is not) to forbid execution of dangerous actions that use the tainted data. When a Perl script running with the -T switch invokes any action that can impact the external host, such as launching a child process, opening a file, or calling exec with a single string argument, that action will be blocked. Although it is possible to test whether a vari- able is tainted, we normally already know which variables contain data from external sources. We must clean the data, untainting it before we can use it. The only way to extract untainted data from a tainted string is through regular expression match variables, using Perl’s pattern matching memory trick using parenthesis placeholders. Wall, Christiansen, and Schwartz in [WCS96] note that “The tainting mechanism is intended to prevent stupid mistakes, not to remove the need for thought.” The taint mechanism will not prevent bad programming practices, such as marking tainted data as untainted without cleaning the data first. Sandbox Malcolm Beattie’s Safe module, part of the standard Perl release, implements the Sandbox pattern. The safe module creates a compartment with well-defined access priv- ileges. A compartment has the following elements: ■■ A name. Each compartment in a Perl program is an instance of a Safe object, initialized with its own capability list. This feature enables a form of multi-level security. We can create several instances of Safe variables and run functions with different access privileges within each one. The Java sandbox, which we will LOW-LEVEL ARCHITECTURE 122 discuss in Chapter 7, “Trusted Code,” supports the same functionality through the creation of multiple policy managers that can be dynamically loaded on the basis of the identity of the applet’s digital signatory or the host from which it was downloaded. ■■ A namespace. The namespace of a safe is restricted. By default, any function executing within the safe can share only a few variables with the externally compiled code that invoked the function within the Safe object. These include the underscore variables $_, @_, and file handles. The code that invokes the safe can add variables to the namespace. ■■ An opcode mask. Each compartment has an associated operator mask. The mask represents a capability list (please refer to Chapter 3, “Security Architecture Basics”) initialized to a default minimal access configuration. The mask is an array of length MAXO( ) bytes, one for each opcode in Perl (of which there are 351 opcodes listed in opcode.h, as of release 5.6.1). A byte is set to 0x00 if execution of the corresponding opcode is allowed or 0x01 if disallowed. Safes can evaluate functions that handled tainted data. The class Safe provides meth- ods for handling namespaces and masks, sharing variables, trapping and permitting opcodes, cleanly setting variables inside the safe, and evaluating strings as Perl code inside the safe. Recall our description of Perl’s run-time mechanism; in particular, the execution phase. When we traverse the syntax tree created from an untrusted script executing within a Safe compartment, we can check the value of the current opcode against the opcode mask very quickly before execution. We can raise an exception if the opcode is not allowed, which helps performance but makes capability list management harder. The Safe class provides methods for opcode mask management, such as conversion of opcode names to mask set- tings and masking or unmasking all opcodes. It is not obvious how to map corporate secu- rity policy to an opcode mask, however. We recommend enforcing the principle of least privilege by turning off all opcodes except the minimal set required. From another viewpoint, the opcode masks correspond to security policies enforced by a Safe object rather than capability lists assigned to code. The opcode mask in the Safe class is bound to the Safe object instance, not to the actual code that is executed within the safe. The two viewpoints are equivalent in most cases; because all functions that can be evaluated within a safe are known at compile time, only the tainted user data to the function changes. This situation contrasts with the Java sandbox, where arbitrary applets can be downloaded and where security policy enforced by the JVM is chosen based on higher-level abstractions, such as the applet’s digital signatory or the host from which it was downloaded. Bytecode Verification in Java We described how Perl’s powerful pattern matching features make input validation eas- ier. At the other extreme, from one-line syntax validation in Perl, is the infinite variety Code Review 123 of formal verification exemplified by theorem provers like the Java bytecode verifier. Theorem provers seek to establish formal and constructive proofs for abstract proper- ties of specific programs. In general, this proof is known to be undecidable, but we can establish constraints on generality to make the problem feasible. The creators of the Java programming language have included security in its design from the start. Comprehensive security has lead to the following situations, however: ■■ Complex specifications that are sometimes open to interpretation. ■■ Implementations of the Java virtual machine and Java libraries that have had bugs. We will discuss some of the security mechanisms of the Java virtual machine in Chapter 7, but we will focus on byte code verification in this section. Java is a complicated, full-fledged language including objects with single inheritance, class hierarchies of objects, interfaces with multiple inheritance, strong typing, strict rules for type conversion, visibility or extensibility properties for object fields and methods ( public, private, protected, abstract, final), and run-time constraints on the operand stack and array bounds. Just running through the list should give you some idea of the complexity of proving a claim about some property of a given Java class. Java 1.2 adds cryptographic extensions and access control mechanisms for granting code permission to execute actions based on rights and performs run-time permissions checking (just in case you thought, after learning about byte code verification, that the hard part was behind us). Java was designed to be portable. Java programs are compiled into bytecodes that are executed on a JVM. The JVM does not trust the compiler to correctly enforce Java lan- guage rules because the bytecodes themselves can be produced using any means, inde- pendent of the Java compiler. Other languages can be compiled into bytecodes, but the JVM must ensure that the resulting bytecodes must be forced to follow Java’s language rules. There are compilers for many languages, including C, C++, Scheme, python, Perl, and Ada95, that produce bytecodes. Nevertheless, the primary language overwhelm- ingly used to produce class files is Java. The initial JVM specification and reference implementation provided by Sun have been extensively studied. Java’s object type model and its attempt at proving properties (“theorems”) about objects have led to considerable research on formal methods for capturing the behavior of the bytecode verifier. In particular, researchers have noted differences between the prose specification and the behavior of the reference imple- mentation. Researchers have used formal methods from type theory to reason about bytecode validity. They have analyzed the verifier’s capabilities as a theorem prover on restricted subsets of the language that remove some complicated language feature in order to apply powerful methods from type theory to understand how to improve and optimize the behavior of bytecode verifiers. The JVM relies on the bytecode verifier to perform static checks to prevent dynamic access violations. Some checks can be implemented immediately while others must be deferred until run time. For example, the bytecode verifier must perform the following actions: LOW-LEVEL ARCHITECTURE 124 [...]... component in Java’s security architecture By default, the JVM trusts only the core Java API All other class files, whether loaded from the local host or over the network, must be verified The other components, including the class loader, the security manager, access controller, cryptographic extensions, and security policies, all depend on the verifier’s ability to vet the code The Java security model suffers... situation is common in architectures that use layers for 148 LO W- L E V E L A R C H I T E CT U R E separating concerns, where each layer offers a cryptographic security solution and all the solutions are turned on simultaneously For example, consider the IP data services protocol of a major wireless data services vendor aimed at providing Internet access on mobile devices The protocol uses RC4 stream cipher... programming style, with assistance and review by security experts when necessary Conclusion Code review is architecture work, but at a very fundamental level Its major virtue is that it adds simplicity to the architecture The more we understand and trust our code, Code Review 127 the less we need to implement complicated patterns to protect and guard components in our architecture Any architect can tell you... verifier’s ability to vet the code The Java security model suffers from one fundamental flaw: complexity The consensus in security architecture is that simplicity is the best route to security Simplicity in design leads to simplicity in implementation and makes reasoning about security possible The goal of Sun in introducing Java as a write once, run anywhere solution to many software problems is a... Pseudo-random sequences must be statistically indistinguishable from a true random source, and public and private keys are derived from large (512, 10 24, or 2 048 bit) prime numbers Random Number Generation All pseudorandom number generators are periodic, but solutions can ensure that any short sequence of pseudorandom bits is indistinguishable from a true random sequence by any computationally feasible... H((Kᮍopad) • H((Kᮍipad) • M)) In this equation, opad is a 64- byte array of the value 0x36; ipad is a 64- byte array of the value Ox5c; ᮍ is exclusive OR; and x • y is the concatenation of x and y The IPSec protocols, discussed in Chapter 8 (“Secure Communications”), use HMACs for message authentication Authentication and Digital Certificates Because the security of asymmetric schemes depends on each principal’s... Alice to Bob Team-Fly® Cr yp t og ra p h y 141 Key Management Key management is an operational challenge How do we generate, secure, distribute, revoke, renew, or replace keys for all the parties in our architecture? Can we recover from key loss? How many keys must we create? Where are these keys stored? Is control distributed or centralized? What assumptions of security are we making about individual... compromised Cryptographic Flaws Any enterprise security policy should consider referencing a comprehensive cryptographic toolkit that provides strong security properties, uses open standards, and provides published test results for assuring validity, performance, and strength When it comes to cryptography, rolling your own solution is a bad idea This action only creates security through obscurity, maintenance... the first indications of errors are often failures caused by 146 LO W- L E V E L A R C H I T E CT U R E deviations from the standard rather than holes in security Code review of cryptographic protocol implementation is again probably outside the domain of the majority of projects, but open standards, open source, published bug fixes, applying security patches, and ensuring that our assumptions about the... the key-scheduling algorithm of RC4 They proposed attacks against WEP vulnerabilities, exploiting those weaknesses Stubblefield, Ioannidis, and Rubin [SIR01] actually implemented one of the attacks to demonstrate that it is practical to do so Ron Rivest proposed a fix for the problem, along with a Cr yp t og ra p h y 147 description of why other protocols using RC4 (such as SSL) are not affected (www.rsa.com/rsalabs) . The consensus in security architecture is that simplicity is the best route to security. Simplicity in design leads to simplicity in implementation and makes reasoning about security possi- ble the class loader, the security manager, access controller, cryptographic extensions, and security policies, all depend on the verifier’s ability to vet the code. The Java security model suffers. solving buffer overflow exploits? Simply put, there are so many solutions to the buffer overflow problem because so many solutions LOW-LEVEL ARCHITECTURE 118 text Program executable Standard C library Safe

Ngày đăng: 14/08/2014, 18:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan