One place for hosting & domains

      Programs

      How To Use argparse to Write Command-Line Programs in Python


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Python’s argparse standard library module is a tool that helps you write command-line interfaces (CLI) over your Python code. You may already be familiar with CLIs: programs like git, ls, grep, and find all expose command-line interfaces that allow you to call an underlying program with specific inputs and options. argparse allows you to call your own custom Python code with command-line arguments similar to how you might invoke git, ls, grep, or find using the command line. You might find this useful if you want to allow other developers to run your code from the command line.

      In this tutorial, you’ll use some of the utilities exposed by Python’s argparse standard library module. You’ll write command-line interfaces that accept positional and optional arguments to control the underlying program’s behavior. You’ll also self-document a CLI by providing help text that can be displayed to other developers who are using your CLI.

      For this tutorial, you’ll write command-line interfaces for a program that keeps track of fish in a fictional aquarium.

      Prerequisites

      To get the most out of this tutorial, we recommend you have:

      • Some familiarity with programming in Python 3. You can review our How To Code in Python 3 tutorial series for background knowledge.

      Writing a Command-Line Program that Accepts a Positional Argument

      You can use the argparse module to write a command-line interface that accepts a positional argument. Positional arguments (as opposed to optional arguments, which we’ll explore in a subsequent section), are generally used to specify required inputs to your program.

      Let’s consider an example CLI that prints the fish in an aquarium tank identified by a positional tank argument.

      To create the CLI, open a file with your text editor:

      Then, add the following Python code:

      aquarium.py

      import argparse
      
      tank_to_fish = {
          "tank_a": "shark, tuna, herring",
          "tank_b": "cod, flounder",
      }
      
      parser = argparse.ArgumentParser(description="List fish in aquarium.")
      parser.add_argument("tank", type=str)
      args = parser.parse_args()
      
      fish = tank_to_fish.get(args.tank, "")
      print(fish)
      

      You can print out the fish in tank_a by running:

      • python3 aquarium.py tank_a

      After running that command, you will receive output like the following:

      Output

      shark, tuna, herring

      Similarly, if you ran aquarium.py to print out the fish in tank_b with:

      • python3 aquarium.py tank_b

      You would receive output like the following:

      Output

      cod, flounder

      Let’s break down the code in aquarium.py.

      First, you import the argparse module to make it available for use in your program. Next, you create a dictionary data structure tank_to_fish that maps tank names (like tank_a and tank_b) to string descriptions of fish held in those tanks.

      You instantiate an instance of the ArgumentParser class and bind it to the parser variable. You can think of parser as the main point of entry for configuring your command-line interface. The description string provided to parser is—as you’ll learn later—used in the automatically generated help text for the CLI exposed by aquarium.py.

      Calling add_argument on parser allows you to add arguments accepted by your command-line interface. In this case, you add a single argument named tank that is a string type. Calling parser.parse_args() instructs parser to process and validate the command-line input passed to aquarium.py (for example, something like tank_a). Accessing the args returned by parser.parse_args() allows you to retrieve the value of the passed in tank argument, and use it to print out the fish in that tank.

      At this point, you’ve written a command-line interface and executed your program to print fish. Now you need to describe how your CLI works to other developers. argparse has strong support for help text to document your CLIs. You’ll learn more about help text next.

      Viewing Help Text

      The aquarium.py file you just wrote in the previous section actually does more than print the fish in a specific tank. Since you’re using argparse, the command-line interface exposed by aquarium.py will automatically include help and usage messages that a user can consult to learn more about your program.

      Consider, for example, the usage message aquarium.py prints if you provide invalid arguments on the command line. Try invoking aquarium.py with the wrong arguments on the command line by running:

      • python3 aquarium.py --not-a-valid-argument

      If you run this command, you’ll receive output like this:

      Output

      usage: aquarium.py [-h] tank aquarium.py: error: the following arguments are required: tank

      The output printed on the command line indicates that there was an error trying to run aquarium.py. The output indicates that the user needs to invoke aquarium.py with a tank argument. Something else you might notice is the -h in-between [] characters. This denotes that -h is an optional argument that you can provide as well.

      Now you’ll find out what happens when you call aquarium.py with the -h option. Try invoking aquarium.py with the -h argument on the command line by running:

      If you run this command, you’ll receive output like this:

      Output

      usage: aquarium.py [-h] tank List fish in aquarium. positional arguments: tank optional arguments: -h, --help show this help message and exit

      As you may have guessed, the -h option is short for, “help.” Running python3 aquarium.py -h (or, equivalently, the longer variant python3 aquarium.py --help) prints out the help text. The help text, effectively, is a longer version of the usage text that was outputted in the previous example when you supplied invalid arguments. Notably, the help text also includes the custom description string of List fish in an aquarium that you instantiated the ArgumentParser with earlier on in this tutorial.

      By default, when you write a CLI using argparse you’ll automatically get help and usage text that you can use to document your CLI for other developers.

      So far, you’ve written a CLI that accepts a required positional argument. In the next section you’ll add an optional argument to your interface to expand on its capabilities.

      Adding an Optional Argument

      Sometimes, it’s helpful to include optional arguments in your command-line interface. These options are typically prefixed with two dash characters, for example --some-option. Let’s rewrite aquarium.py with the following adjusted content that adds an --upper-case option to your CLI:

      aquarium.py

      import argparse
      
      tank_to_fish = {
          "tank_a": "shark, tuna, herring",
          "tank_b": "cod, flounder",
      }
      
      parser = argparse.ArgumentParser(description="List fish in aquarium.")
      parser.add_argument("tank", type=str)
      parser.add_argument("--upper-case", default=False, action="store_true")
      args = parser.parse_args()
      
      fish = tank_to_fish.get(args.tank, "")
      
      if args.upper_case:
          fish = fish.upper()
      
      print(fish)
      

      Try invoking aquarium.py with the new --upper-case argument by running the following:

      • python3 aquarium.py --upper-case tank_a

      If you run this command, you’ll receive output like this:

      Output

      SHARK, TUNA, HERRING

      The fish in tank_a are now outputted in upper case. You accomplished this by adding a new --upper-case option when you called parser.add_argument("--upper-case", default=False, action="store_true"). The "--upper-case" string is the name of the argument you’d like to add.

      If the --upper-case option isn’t provided by the user of the CLI, default=False ensures that its value is set to False by default. action="store_true" controls what happens when the --upper-case option is provided by the CLI user. There are a number of different possible strings supported by the action parameter, but "store_true" stores the value True into the argument, if it is provided on the command line.

      Note that, although the argument is two words separated by a dash (upper-case), argparse makes it available to your code as args.upper_case (with an underscore separator) after you call parser.parse_args(). In general, argparse converts any dashes in the provided arguments into underscores so that you have valid Python identifiers to reference after you call parse_args().

      As before, argparse automatically creates a --help option and documents your command-line interface (including the --upper-case option you just added).

      Try invoking aquarium.py with the --help option again to receive the updated help text:

      • python3 aquarium.py --help

      Your output will be similar to:

      Output

      usage: aquarium.py [-h] [--upper-case] tank List fish in aquarium. positional arguments: tank optional arguments: -h, --help show this help message and exit --upper-case

      argparse automatically documented the positional tank argument, the optional --upper-case option, and the built-in --help option as well.

      This help text is useful, but you can improve it with additional information to help users better understand how they can invoke your program. You’ll explore how to enhance the help text in the next section.

      Exposing Additional Help Text to Your Users

      Developers use the help text provided by your command-line interfaces to understand what your program is capable of and how they should use it. Let’s revise aquarium.py again so it includes better help text. You can specify argument-level information by specifying help strings in the add_argument calls:

      aquarium.py

      import argparse
      
      tank_to_fish = {
          "tank_a": "shark, tuna, herring",
          "tank_b": "cod, flounder",
      }
      
      parser = argparse.ArgumentParser(description="List fish in aquarium.")
      parser.add_argument("tank", type=str, help="Tank to print fish from.")
      parser.add_argument(
          "--upper-case",
          default=False,
          action="store_true",
          help="Upper case the outputted fish.",
      )
      args = parser.parse_args()
      
      fish = tank_to_fish[args.tank]
      
      if args.upper_case:
          fish = fish.upper()
      
      print(fish)
      

      Try invoking aquarium.py with the --help option again to receive the updated help text:

      • python3 aquarium.py --help

      Your output will be the following:

      Output

      usage: aquarium.py [-h] [--upper-case] tank List fish in aquarium. positional arguments: tank Tank to print fish from. optional arguments: -h, --help show this help message and exit --upper-case Upper case the outputted fish.

      In this latest output, notice that the tank positional argument and the --upper-case optional argument both include custom help text. You provided this extra help text by supplying strings to the help part of add_argument. (For example, parser.add_argument("tank", type=str, help="Tank to print fish from.").) argparse takes these strings and renders them for you in the help text output.

      You can improve your help text further by having argparse print out any default values you have defined.

      Displaying Default Values in Help Text

      If you use a custom formatter_class when you instantiate your ArgumentParser instance, argparse will include default values in the help text output. Try adding argparse.ArgumentDefaultsHelpFormatter as your ArgumentParser formatter class:

      aquarium.py

      import argparse
      
      tank_to_fish = {
          "tank_a": "shark, tuna, herring",
          "tank_b": "cod, flounder",
      }
      
      parser = argparse.ArgumentParser(
          description="List fish in aquarium.",
          formatter_class=argparse.ArgumentDefaultsHelpFormatter,
      )
      parser.add_argument("tank", type=str, help="Tank to print fish from.")
      parser.add_argument(
          "--upper-case",
          default=False,
          action="store_true",
          help="Upper case the outputted fish.",
      )
      args = parser.parse_args()
      
      fish = tank_to_fish[args.tank]
      
      if args.upper_case:
          fish = fish.upper()
      
      print(fish)
      

      Now, try invoking aquarium.py with the --help option again to check the updated help text:

      • python3 aquarium.py --help

      After running this command, you’ll receive output like this:

      Output

      usage: aquarium.py [-h] [--upper-case] tank List fish in aquarium. positional arguments: tank Tank to print fish from. optional arguments: -h, --help show this help message and exit --upper-case Upper case the outputted fish. (default: False)

      In this latest output, notice that the documentation for --upper-case ends with an indication of the default value for the --upper-case option (default: False). By including argparse.ArgumentDefaultsHelpFormatter as the formatter_class of your ArgumentParser, argparse automatically started rendering default value information in its help text.

      Conclusion

      The argparse module is a powerful part of the Python standard library that allows you to write command-line interfaces for your code. This tutorial introduced you to the foundations of argparse: you wrote a command-line interface that accepted positional and optional arguments, and exposed help text to the user.

      argparse supports many more feautures that you can use to write command-line programs with sophisticated sets of inputs and validations. From here, you can use the argparse module’s documentation to learn more about other available classes and utilities.



      Source link

      How To Use subprocess to Run External Programs in Python 3


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Python 3 includes the subprocess module for running external programs and reading their outputs in your Python code.

      You might find subprocess useful if you want to use another program on your computer from within your Python code. For example, you might want to invoke git from within your Python code to retrieve files in your project that are tracked in git version control. Since any program you can access on your computer can be controlled by subprocess, the examples shown here will be applicable to any external program you might want to invoke from your Python code.

      subprocess includes several classes and functions, but in this tutorial we’ll cover one of subprocess’s most useful functions: subprocess.run. We’ll review its different uses and main keyword arguments.

      Prerequisites

      To get the most out of this tutorial, it is recommended to have some familiarity with programming in Python 3. You can review these tutorials for the necessary background information:

      Running an External Program

      You can use the subprocess.run function to run an external program from your Python code. First, though, you need to import the subprocess and sys modules into your program:

      import subprocess
      import sys
      
      result = subprocess.run([sys.executable, "-c", "print('ocean')"])
      

      If you run this, you will receive output like the following:

      Output

      ocean

      Let’s review this example:

      • sys.executable is the absolute path to the Python executable that your program was originally invoked with. For example, sys.executable might be a path like /usr/local/bin/python.
      • subprocess.run is given a list of strings consisting of the components of the command we are trying to run. Since the first string we pass is sys.executable, we are instructing subprocess.run to execute a new Python program.
      • The -c component is a python command line option that allows you to pass a string with an entire Python program to execute. In our case, we pass a program that prints the string ocean.

      You can think of each entry in the list that we pass to subprocess.run as being separated by a space. For example, [sys.executable, "-c", "print('ocean')"] translates roughly to /usr/local/bin/python -c "print('ocean')". Note that subprocess automatically quotes the components of the command before trying to run them on the underlying operating system so that, for example, you can pass a filename that has spaces in it.

      Warning: Never pass untrusted input to subprocess.run. Since subprocess.run has the ability to perform arbitrary commands on your computer, malicious actors can use it to manipulate your computer in unexpected ways.

      Capturing Output From an External Program

      Now that we can invoke an external program using subprocess.run, let’s see how we can capture output from that program. For example, this process could be useful if we wanted to use git ls-files to output all your files currently stored under version control.

      Note: The examples shown in this section require Python 3.7 or higher. In particular, the capture_output and text keyword arguments were added in Python 3.7 when it was released in June 2018.

      Let’s add to our previous example:

      import subprocess
      import sys
      
      result = subprocess.run(
          [sys.executable, "-c", "print('ocean')"], capture_output=True, text=True
      )
      print("stdout:", result.stdout)
      print("stderr:", result.stderr)
      

      If we run this code, we’ll receive output like the following:

      Output

      stdout: ocean stderr:

      This example is largely the same as the one introduced in the first section: we are still running a subprocess to print ocean. Importantly, however, we pass the capture_output=True and text=True keyword arguments to subprocess.run.

      subprocess.run returns a subprocess.CompletedProcess object that is bound to result. The subprocess.CompletedProcess object includes details about the external program’s exit code and its output. capture_output=True ensures that result.stdout and result.stderr are filled in with the corresponding output from the external program. By default, result.stdout and result.stderr are bound as bytes, but the text=True keyword argument instructs Python to instead decode the bytes into strings.

      In the output section, stdout is ocean (plus the trailing newline that print adds implicitly), and we have no stderr.

      Let’s try an example that produces a non-empty value for stderr:

      import subprocess
      import sys
      
      result = subprocess.run(
          [sys.executable, "-c", "raise ValueError('oops')"], capture_output=True, text=True
      )
      print("stdout:", result.stdout)
      print("stderr:", result.stderr)
      

      If we run this code, we receive output like the following:

      Output

      stdout: stderr: Traceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops

      This code runs a Python subprocess that immediately raises a ValueError. When we inspect the final result, we see nothing in stdout and a Traceback of our ValueError in stderr. This is because by default Python writes the Traceback of the unhandled exception to stderr.

      Raising an Exception on a Bad Exit Code

      Sometimes it’s useful to raise an exception if a program we run exits with a bad exit code. Programs that exit with a zero code are considered successful, but programs that exit with a non-zero code are considered to have encountered an error. As an example, this pattern could be useful if we wanted to raise an exception in the event that we run git ls-files in a directory that wasn’t actually a git repository.

      We can use the check=True keyword argument to subprocess.run to have an exception raised if the external program returns a non-zero exit code:

      import subprocess
      import sys
      
      result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"], check=True)
      

      If we run this code, we receive output like the following:

      Output

      Traceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 512, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.

      This output shows that we ran a subprocess that raised an error, which is printed in stderr in our terminal. Then subprocess.run dutifully raised a subprocess.CalledProcessError on our behalf in our main Python program.

      Alternatively, the subprocess module also includes the subprocess.CompletedProcess.check_returncode method, which we can invoke for similar effect:

      import subprocess
      import sys
      
      result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"])
      result.check_returncode()
      

      If we run this code, we’ll receive:

      Output

      Traceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 444, in check_returncode raise CalledProcessError(self.returncode, self.args, self.stdout, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.

      Since we didn’t pass check=True to subprocess.run, we successfully bound a subprocess.CompletedProcess instance to result even though our program exited with a non-zero code. Calling result.check_returncode(), however, raises a subprocess.CalledProcessError because it detects the completed process exited with a bad code.

      Using timeout to Exit Programs Early

      subprocess.run includes the timeout argument to allow you to stop an external program if it is taking too long to execute:

      import subprocess
      import sys
      
      result = subprocess.run([sys.executable, "-c", "import time; time.sleep(2)"], timeout=1)
      

      If we run this code, we’ll receive output like the following:

      Output

      Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 491, in run stdout, stderr = process.communicate(input, timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1024, in communicate stdout, stderr = self._communicate(input, endtime, timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1892, in _communicate self.wait(timeout=self._remaining_time(endtime)) File "/usr/local/lib/python3.8/subprocess.py", line 1079, in wait return self._wait(timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1796, in _wait raise TimeoutExpired(self.args, timeout) subprocess.TimeoutExpired: Command '['/usr/local/bin/python', '-c', 'import time; time.sleep(2)']' timed out after 0.9997982999999522 seconds

      The subprocess we tried to run used the time.sleep function to sleep for 2 seconds. However, we passed the timeout=1 keyword argument to subprocess.run to time out our subprocess after 1 second. This explains why our call to subprocess.run ultimately raised a subprocess.TimeoutExpired exception.

      Note that the timeout keyword argument to subprocess.run is approximate. Python will make a best effort to kill the subprocess after the timeout number of seconds, but it won’t necessarily be exact.

      Passing Input to Programs

      Sometimes programs expect input to be passed to them via stdin.

      The input keyword argument to subprocess.run allows you to pass data to the stdin of the subprocess. For example:

      import subprocess
      import sys
      
      result = subprocess.run(
          [sys.executable, "-c", "import sys; print(sys.stdin.read())"], input=b"underwater"
      )
      

      We’ll receive output like the following after running this code:

      Output

      underwater

      In this case, we passed the bytes underwater to input. Our target subprocess used sys.stdin to read the passed in stdin (underwater) and printed it out in our output.

      The input keyword argument can be useful if you want to chain multiple subprocess.run calls together passing the output of one program as the input to another.

      Conclusion

      The subprocess module is a powerful part of the Python standard library that lets you run external programs and inspect their outputs easily. In this tutorial, you have learned to use subprocess.run to control external programs, pass input to them, parse their output, and check their return codes.

      The subprocess module exposes additional classes and utilities that we did not cover in this tutorial. Now that you have a baseline, you can use the subprocess module’s documentation to learn more about other available classes and utilities.



      Source link

      How To Install and Use Radamsa to Fuzz Test Programs and Network Services on Ubuntu 18.04


      The author selected the Electronic Frontier Foundation Inc to receive a donation as part of the Write for DOnations program.

      Introduction

      Security threats are continually becoming more sophisticated, so developers and systems administrators need to take a proactive approach in defending and testing the security of their applications.

      A common method for testing the security of client applications or network services is fuzzing, which involves repeatedly sending invalid or malformed data to the application and analyzing its response. This is useful to help test how resilient and robust the application is to unexpected input, which may include corrupted data or actual attacks.

      Radamsa is an open-source fuzzing tool that can generate test cases based on user-specified input data. Radamsa is fully scriptable, and so far has been successful in finding vulnerabilities in real-world applications, such as Gzip.

      In this tutorial, you will install and use Radamsa to fuzz test command-line and network-based applications using your own test cases.

      Warning: Radamsa is a penetration testing tool which may allow you to identify vulnerabilities or weaknesses in certain systems or applications. You must not use vulnerabilities found with Radamsa for any form of reckless behavior, harm, or malicious exploitation. Vulnerabilities should be ethically reported to the maintainer of the affected application, and not disclosed publicly without explicit permission.

      Prerequisites

      Before you begin this guide you’ll need the following:

      • One Ubuntu 18.04 server set up by following the Initial Server Setup with Ubuntu 18.04, including a sudo non-root user and enabled firewall to block non-essential ports.
      • A command-line or network-based application that you wish to test, for example Gzip, Tcpdump, Bind, Apache, jq, or any other application of your choice. As an example for the purposes of this tutorial, we’ll use jq.

      Warning: Radamsa can cause applications or systems to run unstably or crash, so only run Radamsa in an environment where you are prepared for this, such as a dedicated server. Please also ensure that you have explicit written permission from the owner of a system before conducting fuzz testing against it.

      Once you have these ready, log in to your server as your non-root user to begin.

      Step 1 — Installing Radamsa

      Firstly, you will download and compile Radamsa in order to begin using it on your system. The Radamsa source code is available in the official repository on GitLab.

      Begin by updating the local package index to reflect any new upstream changes:

      Then, install the gcc, git, make, and wget packages needed to compile the source code into an executable binary:

      • sudo apt install gcc git make wget

      After confirming the installation, apt will download and install the specified packages and all of their required dependencies.

      Next, you’ll download a copy of the source code for Radamsa by cloning it from the repository hosted on GitLab:

      • git clone https://gitlab.com/akihe/radamsa.git

      This will create a directory called radamsa, containing the source code for the application. Move into the directory to begin compiling the code:

      Next, you can start the compilation process using make:

      Finally, you can install the compiled Radamsa binary to your $PATH:

      Once this is complete, you can check the installed version to make sure that everything is working:

      Your output will look similar to the following:

      Output

      Radamsa 0.6

      If you see a radamsa: command not found error, double-check that all required dependencies were installed and that there were no errors during compilation.

      Now that you’ve installed Radamsa, you can begin to generate some sample test cases to understand how Radamsa works and what it can be used for.

      Step 2 — Generating Fuzzing Test Cases

      Now that Radamsa has been installed, you can use it to generate some fuzzing test cases.

      A test case is a piece of data that will be used as input to the program that you are testing. For example, if you are fuzz testing an archiving program such as Gzip, a test case may be a file archive that you are attempting to decompress.

      Note: Radamsa will manipulate input data in a wide variety of unexpected ways, including extreme repetition, bit flips, control character injection, and so on. This may cause your terminal session to break or become unstable, so be aware of this before proceeding.

      Firstly, pass a simple piece of text to Radamsa to see what happens:

      • echo "Hello, world!" | radamsa

      This will manipulate (or fuzz) the inputted data and output a test case, for example:

      Output

      Hello,, world!

      In this case, Radamsa added an extra comma between Hello and world. This may not seem like a significant change, but in some applications this may cause the data to be interpreted incorrectly.

      Let’s try again by running the same command. You’ll see different output:

      Output

      Hello, '''''''wor'd!

      This time, multiple single quotes (') were inserted into the string, including one that overwrote the l in world. This particular test case is more likely to result in problems for an application, as single/double quotes are often used to separate different pieces of data in a list.

      Let’s try one more time:

      Output

      Hello, $+$PATHu0000`xcalc`world!

      In this case, Radamsa inserted a shell injection string, which will be useful to test for command injection vulnerabilities in the application that you are testing.

      You’ve used Radamsa to fuzz an input string and produce a series of test cases. Next, you will use Radamsa to fuzz a command-line application.

      Step 3 — Fuzzing a Command-line Application

      In this step, you’ll use Radamsa to fuzz a command-line application and report on any crashes that occur.

      The exact technique for fuzzing each program varies massively, and different methods will be most effective for different programs. However, in this tutorial we will use the example of jq, which is a command-line program for processing JSON data.

      You may use any other similar program as long as it follows the general principle of taking some form of structured or unstructured data, doing something with it, and then outputting a result. For instance this example would also work with Gzip, Grep, bc, tr, and so on.

      If you don’t already have jq installed, you can install it using apt:

      jq will now be installed.

      To begin fuzzing, create a sample JSON file that you’ll use as the input to Radamsa:

      Then, add the following sample JSON data to the file:

      test.json

      {
        "test": "test",
        "array": [
          "item1: foo",
          "item2: bar"
        ]
      }
      

      You can parse this file using jq if you wish to check that the JSON syntax is valid:

      If the JSON is valid, jq will output the file. Otherwise, it will display an error, which you can use to correct the syntax where required.

      Next, fuzz the test JSON file using Radamsa and then pass it to jq. This will cause jq to read the fuzzed/manipulated test case, rather than the original valid JSON data:

      If Radamsa fuzzes the JSON data in a way that it is still syntactically valid, jq will output the data, but with whatever changes Radamsa made to it.

      Alternatively, if Radamsa causes the JSON data to become invalid, jq will display a relevant error. For example:

      Output

      parse error: Expected separator between values at line 5, column 16

      The alternate outcome would be that jq is unable to correctly handle the fuzzed data, causing it to crash or misbehave. This is what you’re really looking for with fuzzing, as this may be indicative of a security vulnerability such as a buffer overflow or command injection.

      In order to more efficiently test for vulnerabilities like this, a Bash script can be used to automate the fuzzing process, including generating test cases, passing them to the target program and capturing any relevant output.

      Create a file named jq-fuzz.sh:

      The exact script content will vary depending on the type of program that you’re fuzzing and the input data, but in the case of jq and other similar programs, the following script suffices.

      Copy the script into your jq-fuzz.sh file:

      jq-fuzz.sh

      #!/bin/bash
      while true; do
        radamsa test.json > input.txt
        jq . input.txt > /dev/null 2>&1
        if [ $? -gt 127 ]; then
          cp input.txt crash-`date +s%.%N`.txt
          echo "Crash found!"
        fi
      done
      

      This script contains a while to make the contents loop repeatedly. Each time the script loops, Radamsa will generate a test case based on test.json and save it to input.txt.

      The input.txt test case will then be run through jq, with all standard and error output redirected to /dev/null to avoid filling up the terminal screen.

      Finally, the exit value of jq is checked. If the exit value is greater than 127, this is indicative of a fatal termination (a crash), then the input data is saved for review at a later date in a file named crash- followed by the current date in Unix seconds and nanoseconds.

      Mark the script as executable and set it running in order to begin automatically fuzz testing jq:

      • chmod +x jq-fuzz.sh
      • ./jq-fuzz.sh

      You can issue CTRL+C at any time to terminate the script. You can then check whether any crashes have been found by using ls to display a directory listing containing any crash files that have been created.

      You may wish to improve your JSON input data since using a more complex input file is likely to improve the quality of your fuzzing results. Avoid using a large file or one that contains a lot of repeated data—an ideal input file is one that is small in size, yet still contains as many ‘complex’ elements as possible. For example, a good input file will contain samples of data stored in all formats, including strings, integers, booleans, lists, and objects, as well as nested data where possible.

      You’ve used Radamsa to fuzz a command-line application. Next, you’ll use Radamsa to fuzz requests to network services.

      Step 4 — Fuzzing Requests to Network Services

      Radamsa can also be used to fuzz network services, either acting as a network client or server. In this step, you’ll use Radamsa to fuzz a network service, with Radamsa acting as the client.

      The purpose of fuzzing network services is to test how resilient a particular network service is to clients sending it malformed and/or malicious data. Many network services such as web servers or DNS servers are usually exposed to the internet, meaning that they are a common target for attackers. A network service that is not sufficiently resistant to receiving malformed data may crash, or even worse fail in an open state, allowing attackers to read sensitive data such as encryption keys or user data.

      The specific technique for fuzzing network services varies enormously depending on the network service in question, however in this example we will use Radamsa to fuzz a basic web server serving static HTML content.

      Firstly, you need to set up the web server to use for testing. You can do this using the built-in development server that comes with the php-cli package. You’ll also need curl in order to test your web server.

      If you don’t have php-cli and/or curl installed, you can install them using apt:

      • sudo apt install php-cli curl

      Next, create a directory to store your web server files in and move into it:

      Then, create a HTML file containing some sample text:

      Add the following to the file:

      index.html

      <h1>Hello, world!</h1>
      

      You can now run your PHP web server. You’ll need to be able to view the web server log while still using another terminal session, so open another terminal session and SSH to your server for this:

      • cd ~/www
      • php -S localhost:8080

      This will output something similar to the following:

      Output

      PHP 7.2.24-0ubuntu0.18.04.1 Development Server started at Wed Jan 1 16:06:41 2020 Listening on http://localhost:8080 Document root is /home/user/www Press Ctrl-C to quit.

      You can now switch back to your original terminal session and test that the web server is working using curl:

      This will output the sample index.html file that you created earlier:

      Output

      <h1>Hello, world!</h1>

      Your web server only needs to be accessible locally, so you should not open any ports on your firewall for it.

      Now that you’ve set up your test web server, you can begin to fuzz test it using Radamsa.

      First, you’ll need to create a sample HTTP request to use as the input data for Radamsa. Create a new file to store this in:

      Then, copy the following sample HTTP request into the file:

      http-request.txt

      GET / HTTP/1.1
      Host: localhost:8080
      User-Agent: test
      Accept: */*
      

      Next, you can use Radamsa to submit this HTTP request to your local web server. In order to do this, you’ll need to use Radamsa as a TCP client, which can be done by specifying an IP address and port to connect to:

      • radamsa -o 127.0.0.1:8080 http-request.txt

      Note: Be aware that using Radamsa as a TCP client will potentially cause malformed/malicious data to be transmitted over the network. This may break things, so be very careful to only access networks that you are authorized to test, or preferably, stick to using the localhost (127.0.0.1) address.

      Finally, if you view the outputted logs for your local web server, you’ll see that it has received the requests, but most likely not processed them as they were invalid/malformed.

      The outputted logs will be visible in your second terminal window:

      Output

      [Wed Jan 1 16:26:49 2020] 127.0.0.1:49334 Invalid request (Unexpected EOF) [Wed Jan 1 16:28:04 2020] 127.0.0.1:49336 Invalid request (Malformed HTTP request) [Wed Jan 1 16:28:05 2020] 127.0.0.1:49338 Invalid request (Malformed HTTP request) [Wed Jan 1 16:28:07 2020] 127.0.0.1:49340 Invalid request (Unexpected EOF) [Wed Jan 1 16:28:08 2020] 127.0.0.1:49342 Invalid request (Malformed HTTP request)

      For optimal results and to ensure that crashes are recorded, you may wish to write an automation script similar to the one used in Step 3. You should also consider using a more complex input file, which may contain additions such as extra HTTP headers.

      You’ve fuzzed a network service using Radamsa acting as a TCP client. Next, you will fuzz a network client with Radamsa acting as a server.

      Step 5 — Fuzzing Network Client Applications

      In this step, you will use Radamsa to fuzz test a network client application. This is achieved by intercepting responses from a network service and fuzzing them before they are received by the client.

      The purpose of this kind of fuzzing is to test how resilient network client applications are to receiving malformed or malicious data from network services. For example, testing a web browser (client) receiving malformed HTML from a web server (network service), or testing a DNS client receiving malformed DNS responses from a DNS server.

      As was the case with fuzzing command-line applications or network services, the exact technique for fuzzing each network client application varies considerably, however in this example you will use whois, which is a simple TCP-based send/receive application.

      The whois application is used to make requests to WHOIS servers and receive WHOIS records as responses. WHOIS operates over TCP port 43 in clear text, making it a good candidate for network-based fuzz testing.

      If you don’t already have whois available, you can install it using apt:

      First, you’ll need to acquire a sample whois response to use as your input data. You can do this by making a whois request and saving the output to a file. You can use any domain you wish here as you’re testing the whois program locally using sample data:

      • whois example.com > whois.txt

      Next, you’ll need to set up Radamsa as a server that serves fuzzed versions of this whois response. You’ll need to be able to continue using your terminal once Radamsa is running in server mode, so it is recommended to open another terminal session and SSH connection to your server for this:

      • radamsa -o :4343 whois.txt -n inf

      Radamsa will now be running in TCP server mode, and will serve a fuzzed version of whois.txt each time a connection is made to the server, no matter what request data is received.

      You can now proceed to testing the whois client application. You’ll need to make a normal whois request for any domain of your choice (it doesn’t have to be the same one that the sample data is for), but with whois pointed to your local Radamsa server:

      • whois -h localhost:4343 example.com

      The response will be your sample data, but fuzzed by Radamsa. You can continue to make requests to the local server as long as Radamsa is running, and it will serve a different fuzzed response each time.

      As with fuzzing network services, to improve the efficiency of this network client fuzz testing and ensure that any crashes are captured, you may wish to write an automation script similar to the one used in Step 3.

      In this final step, you used Radamsa to conduct fuzz testing of a network client application.

      Conclusion

      In this article you set up Radamsa and used it to fuzz a command-line application, a network service, and a network client. You now have the foundational knowledge required to fuzz test your own applications, hopefully with the result of improving their robustness and resistance to attack.

      If you wish to explore Radamsa further, you may wish to review the Radamsa README file in detail, as it contains further technical information and examples of how the tool can be used:

      You may also wish to check out some other fuzzing tools such as American Fuzzy Lop (AFL), which is an advanced fuzzing tool designed for testing binary applications at extremely high speed and accuracy:



      Source link