Code-Assisted Pentests

Many companies use regular penetration tests to check the security of their applications and IT infrastructure. Oftentimes these tests are conducted from the viewpoint of an external attacker (black-box approach), without information about the application or infrastructure itself. This constitutes a tradeoff between testing depth and available time. A comissioned pentest has the goal to find as many vulnerabilities as possible in a given period.

With code-assisted pentesting, the source code or a relevant subset of the code is shared with the pentester during the assessment This method has essential advantages regarding the effectivity and testing depth of the test.

In the following we take a look at the advantages from a company perspective, and show how code-assistance is able to resolve common issues during pentests.

Code-assisted pentesting is efficient

Many penetration tests assume that the test should imitate an external attacker as closely as possible. The indirect question asked is often: Can an external attacker compromise our application?

This is why common pentests are conducted externally using a black-box approach. The pentester has no information about the application, like a real hacker. However, we must consider that a pentester only has limited time for the assessment. This does not apply to a real attacker equally. This means a pentester needs to test very efficiently within the given time frame.

With a code-assisted pentest, the focus can be put on the identification of vulnerabilities and is not dependent on time consuming enumeration tasks. The following examples illustrate common issues with black-box testing.

Issue 1: Enumeration of directories and endpoints

At the beginning of every penetration test it is necessary to get to know the structure of the application and to identify as many endpoints as possible. The endpoints interact with the user and could potentially be vulnerable.

Since the tester accesses the application server externally, he is not able to take a look at the directory or route structure on the server itself. He thus needs to use a word list with possible endpoint names and request every single entry from the application server tofind out which endpoints exist. The following figure shows this process:

cap 1
Figure 1: Enumeration of application endpoints using wordlists

The word list commonly contains more than 100.000 entries. Oftentimes scan repetitions are required to adapt the scanning configuration optimally to the response behaviour of the server.

In case the used word list does not contain the name of an endpoint, the endpoint often remains undiscovered and is not tested. Especially the time constraints of a pentest limit the tester in his possibilities of finding endpoints or routes. Should you include the product name in the wordlist to find potential endpoints? Should you download the homepage of the client and extract all words into a word list for subsequent scans? These are decisions with unknown consequences – they require additional time without increasing the endpoint detection rate predictably.

Without a code-assisted pentest, endpoints and vulnerabilities could remain undiscovered.

Issue 2: Input validation

After an overview of endpoints has been created, the tester needs to identify which interfaces process user inputs and subsequently evaluate them for common vulnerability classes such as Cross-site Scripting (XSS), command injection or SQL injection. Here the issue frequently arises that the application server employs input filtering. However, the tester in a black-box scenario does not know how this filter is implemented.

The following shows a code sample (download.php), that implements the download of system backups in a web application.

<?php
$file = str_replace('../', '', $_GET['file']);
if(isset($file)){ 
   downloadBackup("backups/$file");
}

The application reads in the file parameter of a user request and returns the selected file as a download. To prevent a directory traversal attack, the directory change sequence “../” is filtered with a call of str_replace(). An attacker can thus not execute an immediate attack by submitting a request like GET /download.php?file=../../../../../etc/passwd to download the password file of the system (so called Path Traversal attack). As an external tester, the server input validation logic equally remains unknown.

Due to the time limitation of the penetration test, a tricky game is played, where the tester needs to decide how many malicious inputs need to be tested to make a well-founded decision whether the tested component is secure or not. In our example he could try appending another “../” to his request – maybe he did not use enough traversal sequences to jump into the server’s root directory?

After a few more tests he might come to the conclusion that the component does not show exploitable behaviour. With insight into the code, the vulnerability would have been apparent: The str_replace() function does not replace malicious sequences recursively but just once. An attacker could use the following request to obtain the password file of the server:

GET /download.php?file=….//….//….//….//….//etc/passwd

The str_replace() function replaces all malicious “../” sequences in the file parameter – what remains is a parameter value of: ../../../../../etc/passwd – this is a traversal sequence that allows us to break out of the backup directory!

With a black-box approach it is practically impossible to test all input variants. A code-assisted pentest could identify this vulnerability quickly and efficiently.

Code-assisted pentesting as a solution

Like previously discussed, these issues arise in several phases of a penetration test. The following questions are examples for further topics that can not be covered sufficiently in a black-box test:

  • How are passwords stored in the application? Are customer passwords directly extractable in a compromise or are they saved as “salted hashes”?
  • Are random tokens (e.g., for a user’s password reset) truly random? Does enough entropy exist to prevent brute-force attacks?
  • Was an endpoint forgotten in the implementation of access control checks?

To answer these questions and test critical applications with adequate depth, a code-assisted pentest makes sense. Together with the pentester critical application components are identified and source code is selected for analysis. This means that only necessary code parts are shared with the tester, and not necessarily the entire source repository.

The expense of a code-assisted pentest is only marginally higher compared to a black-box test. This expense is compensated through increased effectivity and testing depth. The result is a pentest that covers more vulnerabilities and delivers more accurate results in a given testing period.