Posts (page 39 of 43)

  • The security pitfalls of the .NET ViewState object have been well-known since its introduction in 2002. The worst mistake is for a developer to treat the object as a black box that will be controlled by the web server and opaque to the end user. Before diving into ViewState security problems we need to explore its internals. This article digs into more technical language1 than others on this site and focuses on reverse engineering the ViewState. Subsequent articles will cover security. To invoke Bette Davis: “Fasten your seat belts. It’s going to be a bumpy night.”2

    The ViewState enables developers to capture transient values of a page, form, or server variables within a hidden form field. The ability to track the “state of the view” (think model-view-controller) within a web page alleviates burdensome server-side state management for situations like re-populating fields during multi-step form submissions, or catching simple form entry errors before the server must get involved in their processing. (MSDN has several articles that explain this in more detail.)

    This serialization of a page’s state involves objects like numbers, strings, arrays, and controls. These “objects” are not just conceptual. The serialization process encodes .NET objects (in the programming sense) into a sequence of bytes in order to take it out of the server’s memory, transfer it inside the web page, and reconstitute it when the browser submits the form.

    Our venture into the belly of the ViewState starts with a blackbox perspective that doesn’t rely on any prior knowledge of the serialization process or content. The exploration doesn’t have to begin this way. You could write .NET introspection code or dive into ViewState-related areas of the Mono project for hints on unwrapping this object. I merely chose this approach as an intellectual challenge because the technique can be generalized to analyzing any unknown binary content.

    The first step is trivial and obvious: decode from Base64. As we’re about to see, the ViewState contains bytes values forbidden from touching the network via an HTTP request. The data must be encoded with Base64 to ensure survival during the round trip from server to browser. If a command-line pydoc base64 or perldoc MIME::Base64 doesn’t help you get started, a simple web search will turn up several ways to decode from Base64. Here’s the beginning of an encoded ViewState:

    /wEPDwUJNzIwNzAyODk0D2...
    

    Now we’ll break out the xxd command to examine the decoded ViewState. One of the easiest steps in reverse engineering is to look for strings because our brains evolved to pick out important words like “donut”, “Password”, and “zombies!” quickly. The following line shows the first 16 bytes that xxd produces from the previous example. To the right of the bytes xxd has written matching ASCII characters for printable values – in this case the string 720702894.

    0000000: ff01 0f0f 0509 3732 3037 3032 3839 340f ......720702894.
    

    Strings have a little more complexity than this example conveys. In an English-centric world words are nicely grouped into arrays of ASCII characters. This means that a programming language like C treats strings as a sequence of bytes followed by a NULL. In this way a program can figure out that the bytes 0x627261696e7300 represent a six-letter word by starting at the string’s declared beginning and stopping at the first NULL (0x00). I’m going to do some hand-waving about the nuances of characters, code points, character encodings and their affect on “strings” as I’ve just described. For the purpose of investigating ViewState we only need to know that strings are not (or are very rarely) NULL-terminated.

    Take another look at the decoded example sequence. I’ve highlighted the bytes that correspond to our target string. As you can see, the byte following 720702894 is 0x0f – not a NULL. Plus, 0x0f appears twice before the string starts, which implies it has some other meaning:

    ff01 0f0f 0509 **3732 3037 3032 3839 340f ......720702894.
    

    The lack of a common terminator indicates that the ViewState serializer employs some other hint to distinguish a string from a number or other type of data. The most common device in data structures or protocols like this is a length delimiter. If we examine the byte before our visually detected string, we’ll see a value that coincidentally matches its length. Count the characters in 720702894.

    ff01 0f0f 0509 3732 3037 3032 3839 340f ......720702894.
    

    Congratulations to anyone who immediately wondered if ViewState strings are limited to 255 characters (the maximum value of a byte). ViewState numbers are a trickier beast to handle. It’s important to figure these out now because we’ll need to apply them to other containers like arrays.3 Here’s an example of numbers and their corresponding ViewState serialization. We need to examine them on the bit level to deduce the encoding scheme.

    Decimal   Hex     Binary  
    1         01      00000001  
    9         09      00001001  
    128       8001    10000000 00000001  
    655321    09ffd9  11011001 11111111 0100111
    

    The important hint is the transition from values below 128 to those above. Seven bits of each byte are used for the number. The high bit tells the parser, “Include the next byte as part of this numeric value.”

    LSB           MSB  
    10000110 00101011
    

    Here’s the same number with the unused “high” bit removed and reordered with the most significant bits first.

    MSB   ...   LSB  
    0101011 0000110 (5510, 0x1586)
    

    Now that we’ve figured out how to pick out strings and their length it’s time to start looking for ways to identify different objects. Since we have strings on the mind, let’s walk back along the ViewState to the byte before the length field. We see 0x05.

    ff01 0f0f 0509 3732 3037 3032 3839 340f ......720702894.
    

    That’s the first clue that 0x05 identifies a string. We confirm this by examining other suspected strings and walking the ViewState until we find a length byte (or bytes) preceded by the expected identifier. There’s a resounding correlation until we find a series of strings back-to-back that lack the 0x05 identifier. Suddenly, we’re faced with an unknown container. Oh dear. Look for the length field for the three strings:

    0000000: 1503 0774 6f70 5f6e 6176 3f68 7474 703a ...top_nav?http:
    0000010: 2f2f 7777 772e 5f5f 5f5f 5f5f 5f2e 636f //www._______.co
    0000020: 6d2f 4162 6f75 7455 732f 436f 6e74 6163 m/AboutUs/Contac
    0000030: 7455 732f 7461 6269 642f 3634 392f 4465 tUs/tabid/649/De
    0000040: 6661 756c 742e 6173 7078 0a43 6f6e 7461 fault.aspx.Conta
    0000050: 6374 2055 73 ct Us
    

    Moving to the first string in this list we see that the preceding byte, 0x03, is a number that luckily matches the amount of strings in our new, unknown object. We peek at the byte before the number and see 0x15. We’ll call this the identifier for a String Array.

    At this point the reverse engineering process is easier if we switch from a completely black box approach to one that references MSDN documentation and output from other tools.

    Two of the most common objects inside a ViewState are Pairs and Triplets. As the name implies, these containers (also called tuples) have two or three members. There’s a catch here, though: They may have empty members. Recall the analysis of numbers. We wondered how upper boundaries (values greater than 255) might be handled, but we didn’t consider the lower bound. How might empty containers be handled? Do they have a length of zero (0x00)? Without diverging too far off course, I’ll provide the hint that NULL strings are 0x658 and the number zero (0) is 0x66.

    The root object of a ViewState is either a Pair or Triplet. Thus, it’s easy to inspect different samples in order to figure out that 0x0f identifies a Pair and 0x10 a Triple. Now we can descend the members to look for other kinds of objects.

    A Pair has two members. This also implies that it doesn’t need a size identifier since there’s no point in encoding “2” for a container that is designed to hold two members. (Likewise “3” for Triplets.) Now examine the ViewState using a recursive descent parser. This basically means that we encounter a byte, update the parsing context based on what the byte signifies, then consume the next byte based on the current context. In practice, this means a sequence of bytes like the following example demonstrates nested Pairs:

    0000000: ff01 0f0f 0509 3732 3037 3032 3839 340f ......720702894.
    0000010: 6416 0666 0f16 021e 0454 6578 7405 793c d..f.....Text.y<
    
    Version
    Pair
     - Member 1: Pair
        - Member 1: String
            “720702894” 
        - Member 2: Pair
           - Member 1: ArrayList (0x16) of 6 elements
               Number 0
               Pair
               ...
           - Member 2: Empty
     - Member 2: Empty
    

    Don’t worry if you finish parsing with 16 or 20 leftover bytes. These correspond to the MD5 or SHA1 hash of the contents. In short, this hash prevents tampering of ViewState data. Recall that the ViewState travels back and forth between the client and server. There are many reasons why the server wants to ensure the integrity of the ViewState data. We’ll explore integrity (hashing), confidentiality (encryption), and other security issues in a future article.

    I haven’t hit every possible control object that might sneak into a ViewState. You can find a NULL-terminated string. You can find RGBA color definitions. And a lot more.

    This was a brief introduction to the ViewState. It’s necessary to understand its basic structure and content before we dive into its security implications. In the next part of this series I’ll expand the analysis to more objects while showing how to use the powerful parsing available from the Boost.Spirit C++ library. We could even dive into JavaScript parsing for those who don’t want to leave the confines of the browser. After that, we’ll look at the security problems due to unexpected ViewState manipulation and the countermeasures for web apps to deploy. In the mean time, I’ll answer questions that pop up in the comments.


    1. More technical, but not rigorously so. Given the desire for brevity, some programming terms like objects, controls, NULL, strings, and numbers (integers signed or unsigned) are thrown about rather casually. 

    2. All About Eve. https://www.imdb.com/title/tt0042192/ (Then treat yourself to Little Foxes and Whatever Happened to Baby Jane?

    3. I say “other containers” because strings can simply be considered a container of bytes, albeit bytes with a particular meaning and restrictions. 

  • CSRF and Beyond Apr 26, 2011

    Two Trees

    Identifying CSRF vulns is more interesting than just scraping HTML for hidden fields or forging requests. CSRF stems from a design issue of HTTP and HTML. An HTML form is effectively vulnerable to CSRF by default. That design is a positive feature for sites – it makes many types of interactions and use cases easy to create. But is also leads to unexpected consequences.

    A passive detection method that is simple to automate looks for the presence or absence of CSRF tokens. However, scraping HTML is prone to errors and generates noisy results that don’t scale well for someone dealing with more than one app at a time. This approach just assumes the identity of a token. It doesn’t verify that it is valid or relied upon by the app. And unless the page is examined after JavaScript has updated the DOM, this technique misses dynamically generated tokens, form fields, and forms.

    An active detection method that can be automated is one that replays requests under different user sessions. This approach follows the assumption that CSRF tokens are unique to a user’s session, such as the session cookie or other pseudo-random value. There’s also a secondary assumption that concurrent sessions are possible. It also requires a browser to deal with dynamic JavaScript and DOM manipulation.

    This active approach basically swaps forms between two sessions for the same user. If the submission succeeds, then it’s more likely request forgery is possible. If the submission fails, then it’s more likely a CSRF countermeasure has blocked it. There’s still potential for false negatives if some static state token or other form field wasn’t updated properly. The benefit of this approach is that it’s not necessary to guess the identity of a token and it’s explicitly testing whether a request can be forged.

    Once more countermeasures become based on the Origin header, the replay approach might be as as simple as setting an off-origin value for this header. A server will either reject or accept the request. This would be a nice, reliable detection as well as a simple, strong countermeasure). Modern browsers released after 2016 support an even better countermeasure – SameSite cookies.

    WhiteHat Security described one way to narrow the scope of CSRF reporting from any form whatsoever to resources carry risk for a user. I’ve slightly modified their three criteria to be resources:

    • with a security context or that cross a security boundary, such as password or profile management
    • that deliver an HTML injection (XSS) or HTTP response splitting payload to a vulnerable page on the target site. This answers the question for people who react to those vulns with, “That’s nice, but so what if you can only hack your own browser.” This seems more geared towards increasing the risk of a pre-existing vuln rather than qualifying it as a CSRF. We’ll come back to this one.
    • where sensitive actions are executed, such as anything involving money, updating a contact list, or sending a message

    There’s an interesting aspect in WhiteHat’s “benign” example. To summarize, imagine a site with a so-called non-obvious CSRF, one XSS vuln, one Local File Inclusion (LFI) vuln, and a CSRF-protected file upload form. The attack uses the non-obvious CSRF to exploit the XSS vuln, which in turn triggers the file upload to exploit the LFI. For example, the attacker creates the JavaScript necessary to upload a file and exploit the LFI, places this payload in an image tag on an unrelated domain, and waits for a victim to visit the booby-trapped page so their browser loads <img src=”https://target.site/xss_inject.page?arg=payload”>.

    This attack was highlighted as a scenario where CSRF detection methods would usually produce false negatives because the vulnerable link, https://target.site/xss_inject.page, doesn’t otherwise affect the user’s security context or perform a sensitive action.

    Let’s review the three vulns:

    • Ability to forge a request to a resource, considered “non-obvious” because the resource doesn’t affect a security context or execute a sensitive action.
    • Presence of HTML injection, HTTP Response Splitting, or other clever injection vuln in said resource.
    • Presence of Local File Inclusion.

    Using XSS to upload a file isn’t a necessarily a vuln (the XSS is, but not the file upload). There’s nothing that says JavaScript within the Same Origin Rule (under which the XSS falls once it’s reflected) can’t use XHR to POST data to a file upload form. In this case it also doesn’t matter if the file upload form has CSRF tokens because the code is executing under the Same Origin Rule and therefore has access the tokens.

    I think these two recommendations would be made by all and accepted by the site developers as necessary:

    • Fix the XSS vulnerability using recommend practices (let’s just assume the arg variable is just reflected in xss_inject.page)
    • Fix the Local File Inclusion (by verifying file content, forcing MIME types, not making the file readable)

    But it was CSRF that started us off on this attack scenario. This leads to the question of how the “non-obvious” CSRF should be reported, especially from an automation perspective:

    • Is a non-obvious CSRF vuln actually obvious if the resource has another vuln like XSS? Does the CSRF become non-reportable once the other vuln has been fixed?
    • Should a non-obvious CSRF vuln be obvious if it has a query string or form fields that might be vulnerable?

    If you already believe CSRF should be on every page, then clearly you would have already marked the example vulnerable just by inspection because it didn’t have an explicit countermeasure. But what about those who don’t follow the tenet that CSRF lurks everywhere? For example, maybe the resource doesn’t affect the user’s state or security context.

    Think about pages that use “referrer” arguments. For example:

    https://web.site/redir.page?url=https://from.here

    In addition to possibly being an open redirect, these are prime targets for XSS with payloads like

    https://web.site/redir.page?url=javascript:arbitrary_payload()

    It seems that in these cases the presence of CSRF just serves to increase the XSS risk rather than be a vuln on its own. Otherwise, you risk producing too much noise by calling any resource with a query string vulnerable. In this case CSRF provides a rejoinder to the comment, “That’s a nice reflected XSS, but you can only hack yourself with it. So what.” Without the XSS vuln you probably wouldn’t waste time protecting that particular resource.

    Look at a few of the other WhiteHat examples. They clearly fall into CSRF vulns – changing shipping address, password reset mechanisms.

    What’s interesting is that they seem to require race conditions or to happen during specific workflows to be successful, e.g. execute the CSRF so the shipping address is changed before the transaction is completed. That neither detracts from the impact nor obviates it as a vuln. Instead, it highlights a more subtle aspect of web security: state management.

    Let’s set aside malicious attackers and consider a beneficent CSRF actor. Our scenario begins with an ecommerce site. The victim, a lucky recipient in this case, has selected an item and placed it into a virtual shopping cart.

    1. The victim (lucky recipient!) fills out a shipping destination.

    2. The attacker (benefactor!) uses a CSRF attack to apply a discount coupon.

    3. The recipient supplies a credit card number.

    4. Maybe the web site is really bad and the benefactor knows that the same coupon can be applied twice. A second CSRF applies another discount.

    5. The recipient completes the transaction.

    6. Our unknown benefactor looks for the new victim of this CSRF attack.

    I chose this Robin Hood-esque scenario to take your attention away from the malicious attacker/victim formula of CSRF to focus on the abuse of workflows.

    A CSRF countermeasure would have prevented the discount coupon from being applied to the transaction, but that wouldn’t fully address the underlying issues here. Consider the state management for this transaction.

    One problem is that the coupon can be applied multiple times. During a normal workflow the site’s UI leads the user through a check-out sequence that must be followed. On the other hand, if the site only prevented users from revisiting the coupon step in the UI, then the site’s developers have forgotten how trivial it is to replay GET and POST requests. This is an example of a state management issue where an action that should be performed only once can be executed multiple times.

    A less obvious problem of state management is the order in which the actions were performed. The user submitted a discount coupon in two different steps: right after the shipping destination and right after providing payment info. In the UI, let’s assume the option to apply a discount shows up only after the user provides payment information. A strict adherence to this transaction’s state management should have rejected the first discount coupon since it arrived out of order.

    Sadly, we have to interrupt this thought to address real-world challenges of web apps. I’ve defined a strict workflow as (1) shipping address required, (2) payment info required, (3) discount coupon optional, (4) confirm transaction required. A site’s UI design influences how strict these steps will be enforced. For example, the checkout process might be a single page that updates with XHR calls as the user fills out each section in any order. Conversely, this single page checkout might enable each step as the user completes them in order.

    UI enforcement cannot guarantee that requests be made in order. This is where decisions have to be made regarding how strictly the sequence is to be enforced. It’s relatively easy to have a server-side state object track these steps and only update itself for requests in the correct order. The challenge is keeping the state flexible enough to deal with users who abandon a shopping cart, or decide at the last minute to add another item before completing the transaction, or a multitude of other actions that affect the state. These aren’t insurmountable challenges, but they induce complexity and require careful testing. This trade-off between coarse state management and granular control leads more to a balance of correctness rather than security. You can still have a secure site if steps can be performed in order of 3, 1, 2, 4 rather than the expected 1, 2, 3, 4.

    CSRF is about requests made in the victim’s session context by the victim’s browser on behalf of the attacker (initiated from an unrelated domain) without the victim’s interaction. If a link, iframe, image tag, or JavaScript causes the victim’s browser to make a request that affects that user’s state in another web site, then the CSRF attack succeeded. The conceptual way to fix CSRF is to identify forged requests and reject them. CSRF tokens are intended to identify legitimate requests because they’re a shared secret between the site and the user’s browser. An attacker who doesn’t know the secret can forge a legitimate request.

    These attacks highlight the soft underbelly of web app state management mechanisms.

    Automated scanners should excel at scaleability and consistent accuracy, but woe to those who believe they fully replace manual testing. Scanners find implementation errors like forgetting to use a prepared statements or not encoding output placed in HTML, but they struggle with understanding design flaws. Complex interactions are more easily understood and analyzed by manual testing.

    CSRF stands astride this gap between automation and manual testing. Automation identifies whether an app accepts forged requests, whereas manual testing can delve deeper into underlying state vulns or chains of exploits that CSRF might enable.

  • The biggest threat to modern web applications is the API – Advanced Persistent Ignorance. Developers rely on all sorts of APIs to build complex software. This one makes code insecure by default. API is the willful disregard of simple, established security designs.

    First, we must step back into history to establish a departure point for ignorance. As one example, almost seven years ago on July 13, 2004 PHP 5.0.0 was officially released. It included this new feature:

    A new MySQL extension named MySQLi for developers using MySQL 4.1 and later. This new extension includes an object-oriented interface in addition to a traditional interface; as well as support for many of MySQL’s new features, such as prepared statements.

    Of course, any new feature can be expected to have bugs and implementation issues. Even with an assumption that serious bugs would take a year to be worked out, that means PHP has had a secure database query mechanism for the past six years.1

    The first OWASP Top 10 list from 2003 mentioned prepared statements as a countermeasure.2 Along with PHP and MySQL, .NET and Java supported these, as did Perl, Python, and Ruby On Rails. In fact, PHP and MySQL trailed other languages and databases in their support for prepared statements.

    SQL injection itself predates the first OWASP Top 10 list by several years. One of the first description of the general class of injection attacks was the 1999 Phrack article, Perl CGI problems. SQL injection was a specialization of these problems to database queries.

    So now we’ve established the age of injection attacks at over a dozen years old and reliable countermeasures at least six years old. These are geologic timescales for the Internet.

    SQL injection vulns shouldn’t exist in 2011.

    Coding mistakes most often imply implementation errors – bugs due to typos, forgetfulness, or syntax. People are fallible, they make mistakes.

    But modern SQL injection vulns are a symptom of bad design. For six years, prepared statements have offered a way to eradicate a class of vulns. It takes actual effort to make them insecure.

    In practice, prepared statements can’t handle every complex query that app owners might want to create, but it can handle the majority of them. And we still see vulns appear in simple queries that could have started out as prepared statements.

    Maybe one of the two billion PHP hobby projects on Sourceforge could be expected to still have these vulns, but not real web sites. Let’s review the previous few months:

    The list may seem short, but there’s an abundance of sites that have had SQL injection vulns. We just don’t have a crowdsourced equivalent for it like xssed.org tracks cross-site scripting.

    XSS is a little more forgivable, though no less embarrassing. HTML injection flaws continue to plague sites because of implementation bugs. There’s no equivalent of the prepared statement for building HTML or HTML snippets. This is why the vuln remains so pervasive: No one has figured out the secure, reliable, and fast way to build HTML with user-supplied data. This doesn’t imply that attempting to do so is a hopeless cause. On the contrary, JavaScript libraries can reduce these problems significantly.

    For all the articles, lists, and books published on SQL injection one must assume that developers are being persistently ignorant of security concepts to such a degree that five years from now we may hear yet again of a database hack that disclosed unencrypted passwords.

    There may in fact be hope for the future. The rush to scaleability and the pious invocation of “cloud” has created a new beast of NoSQL datastores. These NoSQL datastores typically just have key-value pairs with grammars that aren’t so easily corrupted by a stray apostrophe or semi-colon in the way that traditional SQL can be corrupted. Who knows, maybe security conferences will finally do away with presentations on yet another SQL injection exploit and find someone with a novel, new NoSQL Injection vulnerability.

    Advanced Persistent Ignorance isn’t limited to SQL injection vulns. It has just spectacularly manifested itself in them. There are many unsolved problems in information security, but there are also many mostly-solved problems. Big unsolved problems in web security are password resets (overwhelmingly relying on email) and using static credit card numbers to purchase items.

    SQL injection is a mostly-solved problem. Using prepared statements isn’t 100% secure, but it makes a significant improvement. User authentication and password storage is another area of web security rife with errors. Adopting a solution like OpenID can reduce the burden of security around authentication. As with all things crypto-related, using well-maintained libraries and system calls are far superior to writing your own hash function or encryption scheme.

    On the other hand, what does it mean that a mostly-solved problem remains so prevalent? Maybe it’s because software development is more complex than function calls and appsec needs to take more action than just awareness.

    Not all security has to be hard. Nor does it have to impede the developer experience or speed of development. There are crypto and JavaScript libraries that provide design patterns and reuseable components for web sites.

    Education about current development practices can go far, but it needs to be grounded in constructive patterns to follow rather than just mistakes to avoid. In other words, give developers examples of secure design principles and how to adopt those principles. It’s the subtle difference between “Don’t make this list of mistakes” vs. “Here’s how to securely design software.”


    1. MySQL introduced support for prepared statements in version 4.1, whose first alpha release was April 3, 2003. 

    2. “A6 Command Injection Flaws” from OWASPWebApplicationSecurityTopTen-Version1.pdf