This page contains the answers to the questions posed at the end of each chapter of the first edition.
A session is a set of data structures held on the server, which are used to track the state of the user’s interaction with the application. A session token is a unique string that the application maps to the session, and is submitted by the user to reidentify themselves across successive requests.
Defects in the any of the core mechanisms for handling access may enable you to gain unauthorized access to the administrative functionality. Further, data that you submit as a low privileged user may ultimately be displayed to administrative users, enabling you to attack them by submitting malicious data designed to compromise their session when it is viewed.
Yes. If it were not for Step 4, this mechanism would be robust in terms of filtering the specific items it is designed to block. However, because your input is decoded after the filtering steps have been performed, you can simply URL-encode selected characters in your payload to evade the filter:
If Step 4 were performed first (or even not at all) then this bypass would not be possible.
The If-Modified-Since header is used to specify the time at which the browser last received the requested resource. The If-None-Match header is used to specify the entity tag that the server issued with the requested resource when it was last received.
In the two ways described, these headers are used to support caching of content within the browser, and they enable the server to instruct the browser to use a cached copy of a resource, rather than responding with the full contents of the resource if this is not necessary.
The 301 status code tells the browser that the requested resource has moved permanently to a different location. For duration of the current browser session, if your browser needs to access the originally requested resource, it will use the location specified in the 301 response instead.
The 302 status code tells the browser that the requested resource has moved temporarily to a different location. On the next occasion that the browser needs to access the originally requested resource, it will request this from the originally requested location.
The filename CookieAuth.dll indicates that Microsoft ISA server is being used. This is the URL for the login function, and after a successful login the application will redirect to the URL /default.aspx.
The URL is a common fingerprint for the phpBB web forum software. Information about this software is readily available on the Internet, and you can perform your own installation to experiment on. A listing of members can be found at the following URL:
Individual user profiles can be found via URLs like the following:
Various vulnerabilities have been found in the phpBB software so you should confirm the version in use and research any associated problems.
The first response uses the HTTP status code 200, which normally indicates that the request was successful. However, the Content-Location header indicates the location from which the response was retrieved. This appears to be a dynamically generated error page, and includes the value 404 in its query string, indicating that the response contains a customized “file not found” message.
The second response uses the HTTP status code 401, which suggests that the requested resource is present but that users must supply HTTP authentication credentials in order to access it.
In each case, you could substantiate your conclusion by requesting a clearly non-existent item in the same directory with the same extension (for example, /iuwehuiwefuwedw.cpf) and comparing the responses. In the first application, you would expect to see a response very similar to the original. In the second application, you would expect to see a different response containing a “file not found” message.
The defense is trivial to bypass. An attacker does not need to submit the cookie that tracks the number of failed login attempts. They can either disable cookies in their browser, or use an automated script that submits requests without the relevant cookie.
An alternative defense would be to use CAPTCHA controls to slow down an attacker, or to block the source IP address after five failed logins, although this may have an adverse impact where multiple clients are located behind a proxy or a NAT-ting firewall.
(a) The Referer header can be set to an arbitrary value by an attacker, and so is not a safe means of performing any access control checks.
(b) This method will only be effective if the web server containing the diagnostic functions is in a parent or child domain of the originating web server, and the session cookie is appropriately scoped, otherwise the cookie will not be submitted to the diagnostic server. A back-end mechanism will need to be implemented for the diagnostic server to validate the submitted tokens with the originating server.
(c) This method will be effective regardless of the domain name of the diagnostic server. It may be regarded as safe provided that the authentication tokens are not predictable and are transmitted in a secure manner (see Chapter 7). Again, a back-end mechanism for validating tokens will need to be implemented.
There are two basic methods:
(a) You can intercept the request containing the form submission, and add the disabled parameter.
(b) You can intercept the response containing the form, and remove the disabled=true attribute.
(a) The credentials are transmitted within the query string of the URL. These are at risk of unauthorized disclosure via the browser history, the logs of the web server and IDS, or simply by appearing on-screen.
(b) The credentials are transmitted via an unencrypted HTTP connection, making them vulnerable to interception by an attacker who is suitably positioned on the network.
(c) The password is an English word consisting of four lower case alphabetical characters. The application is not enforcing any effective password quality rules.
Self-registration functions are very often vulnerable to username enumeration because users can choose their own username and the application prevents them from registering an existing username.
Applications can avoid self-registration functionality being misused in this way through two methods:
(a) The application can generate its own usernames, assigning a non-predictable username to each new user when they have supplied the required personal information.
(b) The first step of the self-registration process can require users to enter their email address. The application then sends the user an email containing a one-time URL that they can use to continue the registration process. If the supplied email address is already registered, the user is notified of this in the email.
The rationale for requesting two randomly chosen letters from the user’s memorable word, rather than the entire word, is that even if an attacker captures all of the credentials supplied by a user in a single login, it is unlikely that the attacker will be able to repeat the login using those credentials, because a different pair of letters will be requested.
If the application requests all of the required information in a single step, then it must select the randomly chosen letters in advance, without knowing the claimed identity of the authenticating user. This means that an attacker who knows only two letters from a user’s memorable word can simply reload the login form repeatedly until those two letters are requested, enabling them to log in using the captured credentials.
To avoid this defect, the application must choose a new pair of letters following each successful login, and store these in the user’s profile until such time as the user successfully logs in again. When the user has identified themselves at stage one of the login, the pair of letters is retrieved from their profile, and requested from the user. In this way, an attacker who has captured the credentials in a single login will typically need to wait a very long period until the them items are re-requested by the application.
An attacker attempting to guess valid credentials can easily determine whether an individual item is valid or invalid. The application’s behavior effectively enables an attacker to break down the brute-force problem into a series of individual challenges.
The vulnerability can be corrected by continuing through all steps of the login process even if an invalid item is submitted, returning a generic “login failed” message after the final stage, regardless of which item caused the failure. This massively increases the number of requests required to guess a user’s credentials using brute force.
The presence of the anti-phishing mechanism enables an attacker to break the problem of guessing valid credentials into two stages. An attacker can verify whether or not a particular username and date of birth are valid by completing step (a) twice with these values. If the same anti-phishing image is returned on each occasion, then the guessed credentials are almost certainly valid; otherwise, they are not. A scripted attack could quickly iterate through a wide range of dates of birth for a targeted username, in order to guess the correct value.
Worse still, the mechanism devised is not effective in preventing phishing attacks. A cloned web application will receive the username and date of birth supplied by the user in step (a), and can submit these directly to the original application to retrieve the correct image to present to the user in step (b). If users have been told to trust the image to provide assurance of the application’s identity, then the mechanism may actually be counter-productive and may cause users to log in to a phishing site that they would not otherwise trust.
The sessid cookie contains a Base64-encoded string. Decoding the two values you received reveals the following values:
The decoded cookie contains three items of data, separated by semicolons. On first inspection, the three values may contain a username, numeric user identifier, and a changing numeric value. The latter contains 10 digits, and looks like a Unix time value. Translating these two values reveals the following:
Mon, 12 Nov 2007 12:34:23 UTC
Mon, 12 Nov 2007 13:45:32 UTC
These represent the times at which each of your sessions was created.
Hence, it appears that the session tokens are comprised of meaningful user-specific data and a predictable item. In principle, you could mount a brute force attack to guess the tokens issued to other application users.
A 6-character session token allows for a considerably larger range of possible values than a 5-character password. It may therefore appear that the shorter passwords present the most worthwhile target for an attack.
However, there are important differences between brute force attacks targeting passwords and those targeting session tokens. When attempting to guess passwords, it is necessary to supply a username and password together, thus targeting at most one account with each request, and maybe none at all. You may already know some usernames, or be able to enumerate them, or you may need to guess usernames and passwords simultaneously. The login mechanism may contain multiple stages, or be slow to respond. The login mechanism may also enforce account lockout, considerably slowing down your attack.
When attempting to guess session tokens, on the other hand, you can often target multiple users simultaneously. There may be 20 users logged in, or 2000, or zero. If a user is not presently logged in, then you cannot target them in this way. There is no easy way for an application to enforce any kind of “lockout” when a large number of invalid tokens are received. Token guessing attacks normally run very quickly – requests containing an invalid token usually receive a fast response containing an error message or redirection.
In short, there is no definitive answer to the question, and the most worthwhile target will depend upon your purposes and other aspects of the application. If many users are logged in and you simply need to compromise any user, then targeting sessions may be best. If you wish to compromise the single administrative account, which rarely logs in, then a password guessing attack will be more effective.
(a) Yes. The domain and path both match the scope of the cookie.
(b) No. The domain is not the same or a subdomain of the domain scope of the cookie.
(c) Yes. The domain is a subdomain of the domain specified in the scope, and the path matches the scope.
(d) Yes. The domain and path both match the scope of the cookie. Although the protocol is HTTP, the secure flag was not specified, so the cookie is still transmitted.
(e) Yes. The domain matches the scope of the cookie. Because the path scope did not include a trailing slash after /login, the scope includes not only the path /login/ but also any other patch matching the /login prefix.
(f) No. The path does not match the scope of the cookie.
(g) No. The domain is the parent of the domain specified in the scope, and so is not included.
(h) No. The domain is not the same or a subdomain of the domain scope of the cookie.
The logout function is broken.
The script invalidates the session token currently held in the browser, meaning that its previous value will not be submitted in any subsequent requests. It then initiates a redirection to the application start page. Any attempt to access protected functionality will be denied because the request will not be made as part of an authenticated session.
However, the client-side application does not communicate to the server that a logout action has been performed. The user’s session on the server will remain active, and the token previously issued will continue to be accepted if issued to the server. This will remain the case indefinitely until the session is timed out or otherwise cleaned up. During that interval, an attacker who has captured or guessed the token’s value through some means can continue to use it to hijack the user’s session.
You should attempt the following tests, in order of effectiveness:
(a) Modify the uid value to a different value with the same syntactic form. If your own account details are still returned, then the application is probably not vulnerable.
(b) If you are able to register or otherwise access a different user account, log in using that account to obtain the other user’s uid value. Using your original user context, substitute this new uid in place of your own. If sensitive data about the other user is displayed, then the application is vulnerable.
(c) Use a script to iterate up and down for a few thousand values from your uid, and determine whether any other users’ details are returned.
(d) Use a script to request random uid values between 0 and 9999999999 (in the present example) and determine whether any other users’ details are returned.
It is possible to spoof another user’s IP address, although in practice this may be extremely difficult. More significantly, different end users on the Internet may share the same IP address if they are behind the same web proxy or NAT-ting firewall.
One way in which IP-based access controls can be effective in this situation is as a defense-in-depth measure to ensure that users attempting to access administrative functions are located on the organization’s internal network. Those functions should also, of course, be protected by robust authentication and session handling mechanisms.
There is no horizontal or vertical segregation of access within the application, so there is no need for any access controls that discriminate between different individual users.
Even though all users are in the same category, the application still needs to restrict the actions that any user can perform. A robust solution will use the principle of least privilege to ensure that all user roles within the application’s architecture have the minimum permissions necessary for the application to function. For example, because users only need read access to data, the application should access the database using a low privileged account with read-only permissions to only the relevant tables.
You can determine the number of columns in two easy ways. First, you can SELECT the type-neutral value NULL from each column, increasing the number of columns until the application returns data, indicating that the correct number of columns were specified, for example:
' UNION SELECT NULL--
' UNION SELECT NULL, NULL--
' UNION SELECT NULL, NULL, NULL--
Note that on Oracle you will need to add FROM DUAL after the final NULL in each case.
Second, you can inject ORDER BY clauses and increment the specified column until an error occurs, indicating that an invalid column was requested:
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
An easy way to confirm the database type is to use database-specific string concatenation syntax to construct some benign input within the query you control. For example, if the original value of the parameter is London you can submit the following items in turn:
If the first results in the same behavior as the original, the database is probably Oracle. If the second results in the same behavior, the database is probably MS-SQL.
While it may seem counterintuitive, the user registration function is probably the safest. Registration functions normally use INSERT statements, which are unlikely to affect other records if you modify them. A function to update personal records is probably using conditional UPDATE statements. If you inject a payload like ' or 1=1-- you may cause all records in the table to be modified. Similarly, the function to unsubscribe is probably using conditional DELETE statements, and could impact on other users if you are not careful.
That said, it is impossible to be completely certain in advance which statements are being carried out by any kind of functionality, and you should advise the application owner of the risks before you perform the test.
You can SQL comment characters to separate keywords and other items in your injected payloads, for example:
You can use the CHAR command to return a string value from a numeric ASCII character code. For example, on Oracle the string FOO can be represented as:
Your attack appears to have successfully traversed to the /etc directory, however the application is appending .log to your supplied filename. You can try a null byte attack to cause the file system API to truncate the resulting filename at the end of your input:
The application appears to be performing some sanitization on your input before it is processed by the file system. It is possible that the application is stripping ../ sequences. To test for this filter, you should supply the following input:
It is also possible that the application is replacing ../ with ./ instead. To test for this filter, you should supply the following input:
If either of these inputs succeeds in retrieving the file foo.txt then you have probably determined how the filter works. Your next step should be to use multiple instances of your successful traversal sequence to attempt to step above the starting directory.
Attempting to retrieve the default file boot.ini resulted in a built-in access denied message. It is likely that your attack succeeded in traversing to the target file, but that the web server process does not have permissions to read this file (which by default can only be read by Administrators, Power Users and System). You can confirm this hypothesis by requesting a nonexistent file, such as:
and determining whether you receive a different response – probably an error message indicating that the requested file was not found.
If this test is successful, you can proceed to retrieve arbitrary files from the file system that the web server does have permission to access. For example, given that you know the starting directory from which the files are being retrieved, you can traverse via the web root to retrieve the source code to the file customize.asp itself:
If quotation marks are doubled up before the length limit is enforced, then you can introduce an odd number of single quotes into your input by causing the input to be truncated in between two doubled up quotes (see Chapter 9).
If the length limit is applied before the doubling up, then you may still be able to exploit any buffer overflow conditions by placing a large number of single quotes at the start of your payload, causing this to extend sufficiently far to overflow the buffer with crafted data positioned towards the end of your payload.
Using valid credentials for an account you control, you should repeat the login process numerous times, modifying your requests in specific ways:
For each parameter submitted, try submitting an empty value, omitting the name/value pair altogether, and submitting the same item multiple times with different values.
If the process involves multiple stages, try performing these stages in a different sequence, skipping individual stages altogether, proceeding directly to arbitrary stages, and submitting parameters at stages where they are not expected.
If the same item of data is submitted more than once, probe to determine how each value is processed, and whether data that is validated at one stage is trusted later on.
The application may well be performing these two checks independently, validating the password against one username and the token’s value against a different username, and then creating an authenticated session in the context of one of the validated usernames.
If an application user who possesses their own physical token manages to obtain the password of another user, they may be able to login as that user. Conversely, depending on how the logic functions, a user who can read the value from another user’s token may be able to login as that user without knowing their password. The overall security posture of the solution is thus significantly diminished.
Even if a target user is not logged in at the time of the attack, they may still be compromised. If the application is vulnerable to session fixation, then an attacker can capture their token and wait for them to log in. An attacker can inject code into the login page to capture keystrokes, or even present a Trojan login form which sends their credentials elsewhere.
There are countless different attack payloads for XSS exploits. Some of the more commonly discussed payloads are:
stealing the session cookie;
inducing user actions;
injecting Trojan functionality;
stealing cached autocomplete data; and
You can perform a request forgery attack, by injecting an arbitrary URL into the image tag that is crafted to perform some malicious action. When a user views the page, the action will be performed within their user context.
You can perform a stored XSS attack by injecting arbitrary script into an event handler, for example:
You can “convert” the reflected XSS flaw into a DOM-based one. For example, if the vulnerable parameter is called vuln, you can use the following URL to execute an arbitrarily long script:
/script.asp?vuln=<script>eval(location.hash.substr(1))</script>#alert('long script here ......')
(a) Arbitrary redirection to lend credibility to a phishing attack.
(b) Injection of a cookie header to exploit a session fixation flaw.
(c) A response splitting attack to poison the cache of a proxy server.
An attacker must be able to determine all of the relevant parameters to the function in advance – that is, they must not contain any secret or unpredictable values that an attacker cannot set without already having hijacked a victim’s session.
(a) The standard anti-XSRF defense of including an unpredictable parameter within requests for JSON objects containing sensitive data.
(c) The mandatory use of the POST method for retrieval of JSON objects.
(a) HTTP status code
(b) Response length
(c) Contents of response body
(d) Contents of Location header
(e) Setting of any cookies
(f) Occurrence of any time delays
There are no definitive answers to this question. The following are examples of fuzz strings that are suitable for testing each of the categories of vulnerability. There are many other strings that would be equally suitable.
'; waitfor delay '0:0:30'--
(b) ||ping -i 30 127.0.0.1;x||ping -n 30 127.0.0.1 &
In many situations, modifying a parameter’s value in some way will result in an error, causing the application to stop the rest of its processing on that request. The application will not therefore execute various code paths in which the other parameters may be processed in unsafe ways.
One effective way to ensure that you achieve a decent level of code coverage with your automated fuzzing is to use a benign request as your template, and to modify each parameter in turn, leaving the other parameter with their initial values. You can then go on to perform manual testing of multiple parameters simultaneously, based on the results of the fuzz testing and your understanding of the role of each parameter. If time permits, you can also go on to perform more elaborate fuzzing, changing multiple parameters simultaneously using different permutations of payloads.
Often, in addition to the redirection, the application will set a new cookie when you submit to valid credentials, assigning you an authenticated session that will result in different content when you follow the redirection. If this is the case, then you can use the presence of a Set-Cookie header as a reliable indicator of a hit.
If this is not the case, and the application simply upgrades your existing session when you submit valid credentials, then your script will probably need to follow the target of the redirection and inspect the contents of the resulting page to determine whether you have successfully logged in.
The application is inserting your input directly into a dynamically constructed query. However, it appears to be stripping any whitespace characters from your input, as can be seen from the expression having1 that appears in the error message.
The condition is certainly exploitable. You can use SQL comments instead of whitespace to separate items of syntax in your query, for example:
which returns the different error message
Server: Msg 8118, Level 16, State 1, Line 1
Column 'users.ID' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
This confirms that the condition can be exploited and completes the first step in enumerating the structure of the query being performed.
This is a system-generated error message produced by cgiwrap. It indicates that the script you requested cannot be executed on the server because it does not have suitable file permissions. This script is therefore probably of little interest to you.
The error message contains some information that may be of use, including an email address. More significantly, however, it contains various details that have been copied from the client request. You should probe the server’s handling of crafted input in the relevant request headers to see if the error message is vulnerable to XSS. Note that a user can be induced to make a request containing arbitrary request headers via a Flash object.
You supplied the value admin in the name parameter. The error message indicates that the application attempted (and failed) to connect to a database on a host named admin. It appears that the application is letting you control the database that it will use to fulfill the request.
You should try submitting the IP address or hostname for a server that you control, and see if the application connects to you. You should also try to guess a range of IP addresses within the internal network, to see if you can probe for other databases reachable from the application server.
Given that the supplied hostname has been copied into the error message, you should also investigate whether the application is vulnerable to XSS. Peripheral content such as error messages is often subject to less rigorous input validation and other controls than the primary functionality within the application.
With stack-based overflows, you can usually get immediate control of the saved return address on the stack, and therefore the instruction pointer when the current function returns. You can point the instruction pointer at an arbitrary address containing your shellcode (usually within the same buffer that triggers the overflow).
With heap-based overflows, you can usually set an arbitrary pointer in memory to an arbitrary value. Leveraging this modification to take control of the flow of execution will normally involve a further step. Further, once a heap buffer has been overflowed, your attack may not execute immediately but may depend upon unpredictable events that impinge upon the allocation of heap memory.
Although it may be relatively easy to probe for and detect a buffer overflow vulnerability in a remote web application, developing a working exploit for the bug will in general be extremely difficult (though not absolutely impossible).
With local access to a vulnerable network device, on the other hand, it is possible to attach debugging equipment and fully investigate the nature of the vulnerability, and thereby develop a finely crafted attack to exploit it reliably.
The %n format specifier has been disabled by default in the latest implementations of the printf family of functions. Hence, you should always supply a large number of %s specifiers, which will always be supported and are very likely to trigger an exception if your input is handled in an unsafe way.
Further, using only the printf family of specifiers will not detect vulnerable calls to other formatting functions, such as FormatMessage.
You have probably identified a heap overflow vulnerability. Every overlong request you are submitting is probably causing corruption of the heap control structures. However, heap corruption normally only results in an exception when a relevant heap operation takes place, and the timing of this operation may depend on other events that are unrelated to the requests you are submitting.
Note that in some rare situations, the same behavior may occur for different reasons – for example, because of load balancing or deferred processing of your input.
Nevertheless, it is probably possible to modify the content returned to users, since some of this will be generated using data contained within the database. For example, even if the application contains no stored XSS vulnerabilities that can be triggered within the application itself, you may be able to inject arbitrary scripts into the application’s responses by modifying data directly within the database. If this enables you to attack an administrative user then you may quickly be able to compromise the entire application.
A web server will display a directory listing if you request a URL for a directory and:
(a) the web server cannot find a default document such as index.html;
(b) directory listings are enabled;
(c) you have the required permissions to access the directory.
WebDAV methods allow web-based authoring of web content.
These methods may be dangerous if they are not subjected to strict access control. Further, because of the complex functionality involved, they have historically been a source of vulnerabilities within web servers – for example, as an attack vector for exploiting operating system vulnerabilities via the IIS server.
If the proxy allows connection back out to the Internet, you could use it to attack third party web applications on the Internet, with your requests appearing to originate from the misconfigured web server.
Even if requests to the Internet are blocked, you may be able to leverage the proxy to access web servers within the organization that are not directly accessible, or to reach other web-based services on the server itself.
The PL/SQL Exclusion List is a pattern-matching blacklist designed to prevent the PL/SQL gateway from being used to access certain powerful database packages.
Various bypasses have been discovered to the PL/SQL Exclusion List filter. These essentially arise because the filter employs very simple expressions, while the back-end database follows much more complex rules to interpret the significance of the input. Numerous ways have been discovered of crafting input that does not match the blacklist patterns but nevertheless succeeds in executing the powerful packages within the database.
(a) Cross-site scripting
(b) SQL injection
(c) Path traversal
(d) Arbitrary redirection
(e) OS command injection
(f) Backdoor passwords
(g) Some native software bugs
Method 1 is more secure.
Although method 1 constructs a SQL query dynamically from user input, it doubles up all single quotation marks that appear within that input, and all user-supplied parameters are treated as string data. Although this is not the best practice way to handle SQL queries safely, there does not appear to be any opportunity for SQL injection in the present case.
Method 2 uses a parameterized query, which is the preferred way to incorporate user-supplied data into SQL statements in a safe way. However, only two of the three items of user input are properly parameterized. One of the items is erroneously placed directly into the string that specifies the query structure, and so the application is fully vulnerable to SQL injection.
The application may be vulnerable to reflected XSS, because it appears that an on-screen welcome message is being constructed directly from a request parameter.
It is not possible to confirm conclusively whether the application is vulnerable from this code snippet alone. To do this, you would need to investigate:
(a) whether any input validation is performed on the name parameter elsewhere; and
(b) whether any output validation is performed on m_welcomeMessage before it is copied into the application’s response.
Yes. With knowledge of the token creation algorithm used, it is possible to extrapolate forwards and backwards to identify all tokens created by the application, on the basis of a single sampled token.
The Java API java.util.Random implements a linear congruential generator that generates pseudo-random numbers according to a fully predictable algorithm. Knowing the state of the generator at any iteration, it is possible derive the sequence of numbers that it will generate next, and (with a little number theory) derive the sequence that it generated previously.
However, the java.util.Random generator maintains 48 bits of state, and the nextInt method only returns 32 bits of that state. Capturing a single output from the nextInt method is not sufficient to determine the state of the generator, or to predict its sequence of outputs.
In the present case, this difficulty can be easily circumvented because the algorithm used by the application makes two successive calls to nextInt. Each session token created contains 32 bits of state from one iteration of the generator, and 32 bits of state from the next iteration. Given this information, it is straightforward to perform a local brute force exercise to discover the missing 16 bits of state at the first iteration (by trying each possible permutation of the missing 16 bits, and testing whether the generator outputs the 32 bits captured from the second iteration). Once the missing 16 bits have been confirmed, the full state of the generator is known, and you can proceed to derive subsequent and earlier outputs in the standard way.
Ironically, the developers’ decision to make two calls to nextInt and combine the results renders the token creation algorithm more vulnerable than it would otherwise be.
See the following paper by Chris Anley for more details of this type of attack:
Copyright © 2011 Dafydd Stuttard and Marcus Pinto. All rights reserved.