đ Welcome to Blog Number 2 of our âPython for Hackersâ series! Today, weâre building an âFTP bruteforcer using asynchronous programmingâ. If you enjoyed our previous blog on SSH brute-forcing, youâre in for a treat! In this blog, we will learn about FTP and how it can be attacked using brute force techniques. For those of you who donât know Asynchronous programming or havenât read my previous blog, donât worry I will discuss the topic of Asynchronous Programming here as well.
Some of the code syntaxes will match my previous ssh-bruteforce script, as both tools donât require huge code changes.
đWhat is FTP?
FTP stands for File Transfer Protocol. It is a standard network protocol used for transferring files between a client and a server on a computer network. FTP enables users to upload, download, and manage files on remote servers over a TCP/IP network.
The control connection is established on TCP port 21 and is responsible for sending commands and receiving responses between the client and the server
In short, Ftp is a protocol used to transfer files, It has one server called the ftp server which hosts all the files, and has other node as Ftp client which recieves files from the server.
To login in a server, you would need a host IP address or hostname , username (the user which files you are going to look into) and lastly you will need the password of the user.
Example:
đĄ Building The tool
đĄBefore building the tool itself let me give a brief of what you are going to build.
â Taking command-line arguments
You may have seen various tools which take input from the command line itself, which provides flexibility, enabling users to tailor the toolâs functionality to their specific needs without modifying the source code. Using command-line arguments helps us in attaining various things such as
- Reproducibility: By specifying the exact options and parameters used during a particular run, the same results can be obtained consistently in subsequent executions
- Automation and scripting: Command-line tools are ideal for automation and scripting purposes. They can be easily incorporated into scripts or batch files, allowing repetitive tasks to be automated.
- Documentation and discovery: Command-line tools typically come with built-in help and usage information. By invoking the tool with specific command-line arguments like
--help
or -h
Using argparse to define command-line arguments
In Python, Argparse provides a convenient way to parse command-line arguments. It simplifies the process of defining, organizing, and handling command-line options and arguments in Python scripts.
Before using it, you will have to install it using the below command
Before using it, you will have to install it using the below command
pip install argparse
Basic Example
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('--name',type=str)
arguments=parser.parse_args()
print(f"HI! {arguments.name}")
Letâs understand this code:
import argparse
: This line imports the argparse
module
parser = argparse.ArgumentParser()
: Here, an instance of the ArgumentParser
class is created. This object is responsible for defining and parsing the command-line arguments.
parser.add_argument('--name', type=str)
: This line adds a command-line argument to the parser. The argument is specified with the --name
flag, indicating that it is an optional argument. The type=str
argument specifies that the argumentâs value should be interpreted as a string. You can add various optional arguments like the required which tells that the argument is essential for the tool to run or not.
arguments = parser.parse_args()
: This line triggers the parsing of the command-line arguments. The parse_args()
method examines the command line, extracts the provided arguments, and stores them in the arguments
object.
print(f"HI! {arguments.name}")
: Finally, the value of the name
argument is accessed using arguments.name
and printed with a greeting message.
As you can see we are able to the command-line argument that we created.
Argparse, by default also creates a documentation of tool which can be accessed through -h
or --help
.
To get detailed info on the argparse you can check out : Link
Creating get_args() function
So now we know the basic overview of the argparse, so now letâs start creating what we will need in our tool.
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('target', help='Host to attack on e.g. 10.10.10.10.')
parser.add_argument('-p', '--port', dest='port', default=21,
type=int, required=False, help="Port to attack on, Default:21")
parser.add_argument('-w', '--wordlist', dest='wordlist',
required=True, type=str)
parser.add_argument('-u', '--username', dest='username',
required=True, help="Username with which bruteforce to ")
arguments = parser.parse_args()
return arguments
I think the initial code is well explained, so I will explain this code
parser.add_argument('target', help='Host to attack on e.g. 10.10.10.10.')
'target'
is the name of the argument being added. In this case, it represents the target host to attack. help='Host to attack on e.g. 10.10.10.10.'
is an optional argument for the add_argument
, it sets help message that will be displayed when the user requests help or usage information for the script.
parser.add_argument('-p', '--port', dest='port', default=21,
type=int, required=False, help="Port to attack on, Default:21")
-p
is the short form of the command-line argument, --port
is the long form of the command-line argument, you can use both. dest='port'
sets the destination attribute name for the parsed argument. In this case, the parsed value will be stored in the variable port
, default=21
sets the default value to 21 (ftp works by default on port 21, you can specify another value as well! ), type=int
indicates that the value of the argument should be considered an integer value, required=False
specifies that the specifying argument in the command-line is not necessary.
and I have already discussed what help
does.
I think with this much info on arguments you can figure out the rest of the code by yourself, In this function after retrieving command-line arguments we just return the values to be further retrieved.
Getting colored outputs using termcolor
You have seen several tools which generate output in color format which enhances the tool or script by providing it enhanced readability and error identification.
In Python, you can use the termcolor module for colors in your output. The best thing about this, it is platform-independent so it works on almost all the platforms.
Installation
pip install termcolor
Basic usage
from termcolor import colored, cprint
# using print
print(colored("Hello Guys", "red"))
# using cprint
cprint("Hello Guys", "green")
There are two functions in the termcolor library that help in colors to the output: colored
and cprint
.
In the first example, print(colored("Hello Guys", "red"))
, the colored
function is called with two arguments: the text string "Hello Guys"
and the color "red"
. This function returns the text string wrapped in special ANSI escape codes that represent the specified color. The print
function then outputs the colored text to the console.
In the second example, cprint("Hello Guys", "green")
, the cprint
function is used directly to print colored text. It takes the same arguments as the print
function, with the text string and the desired color.
To see the various types of color it supports, you can visit : Link
đ„ Asynchronous Programming
Asynchronous programming is a programming paradigm that allows tasks to be executed concurrently and independently, without waiting for each other to complete. In simpler words, it enables a program to perform multiple operations simultaneously, making it more efficient and responsive.
In traditional synchronous programming, tasks are executed one after another, and each task must complete before the next one can start. This can lead to delays and inefficiencies, especially when dealing with tasks that involve waiting, such as network requests or file operations. Asynchronous programming, on the other hand, allows the program to continue executing other tasks while waiting for certain operations to complete.
đ How is Asynchronous Programming different from multiprocessing, multithreading
Although multiprocessing, multithreading, and Asynchronous programming are all used to deliver concurrent execution in a program, they are different in the ways these technologies work.
Multiprocessing involves creating multiple processes. Each process has its own memory space and resources, providing true parallel execution. Multiprocessing is suitable for CPU-bound tasks that can benefit from utilizing multiple processor cores effectively. Multiprocessing is suitable for CPU-bound tasks that can benefit from utilizing multiple processor cores effectively. This process takes the most resources.
Multithreading involves creating multiple threads within a single process. Each thread represents a separate flow of execution, allowing multiple tasks to run concurrently.
Threads share the same memory space and resources of the process they belong to. This can be useful for CPU-bound tasks where multiple threads can perform computations in parallel. This takes less resources compared to multiprocessing.
Asynchronous Programming involves using non-blocking operations and event-driven mechanisms to achieve parallelism. Instead of creating separate threads or processes, asynchronous programming focuses on efficient task handling and continues executing other tasks while waiting for certain operations to complete.
đ Example-based learning
Imagine you are a fruit shop owner who has just set up your shop where you sell fruits. After a month you see that your business has been expanding and there are several customers buying from your shop.
Now to control that rush you can do three things
- Hire additional staff to assist you with various stocks. Each staff member would be responsible for a specific task, such as one person attending to customersâ inquiries, another person arranging the displays, and a third person handling the cash counter. They would work in parallel, allowing you to serve customers more effectively and maintain a well-organized shop. After your work is done, you can simply fire these staff. This is the simple example of Multithreading and in this additional staff is the threads
- Opening a new fruit shop: having multiple separate fruit shops, each with its own set of employees, resources, and responsibilities. In this one shop could be dedicated to handling customer assistance, another shop could focus on restocking, and a third shop could manage the cash counter. This is the example of multiprocessing
- Efficiently handling multiple tasks : Example: While a customer is selecting fruits, you can start restocking the shelves with fresh produce and also handle the payment of another customer at the cash counter. So you donât need separate employees and separate shops for various tasks. This is an example of Asynchronous programming
đ„ In short Asynchronous programming uses the main thread and switches over the tasks even ever a task is taking time to execute.
đ„ Using Asynchronous Programming in Python
In Python, asynchronous programming is supported through the asyncio
module, which provides tools and utilities for writing asynchronous code. Hereâs a simple example to illustrate the basic concepts:
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(1) # Simulating some time-consuming operation
print("Task 1 completed")
async def task2():
print("Task 2 started")
await asyncio.sleep(2) # Simulating another time-consuming operation
print("Task 2 completed")
async def main():
# Creating tasks and scheduling them
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
# Allowing tasks to run concurrently
await asyncio.gather(t1, t2)
# Running the main function
asyncio.run(main())
Output:
Understanding Elements from code
Corroutine: A coroutine is a special type of function or routine that can be paused and resumed at specific points during its execution. In Python, coroutines are defined using the async
keyword before the function declaration.
To define a coroutine, just add async keyword before the function declaration.
async def task():
statement1
statement2
Event Loop: An event loop keeps track of all the registered tasks and decides when and in what order they should be executed. It ensures that tasks progress without blocking each other and maximizes the utilization of system resources.
Await: The await
keyword is used in Python within asynchronous functions or coroutines to pause the execution of a coroutine until a particular asynchronous operation is complete. It is typically used with functions or methods that return awaitable objects, such as asyncio.sleep()
or asyncio.gather()
.
import asyncio
async def my_async_function():
print("Before await")
await asyncio.sleep(2) # Pause the coroutine for 2 seconds
print("After await")
asyncio.run(my_async_function())
asyncio.create_task(): asyncio.create_task()
is a function provided by the asyncio
module in Python. It is used to create a task that represents the execution of an asynchronous function or coroutine. The task can then be scheduled and run concurrently with other tasks using the event loop.
asyncio.gather(): asyncio.gather()
allows you to execute multiple awaitable objects concurrently. It schedules the awaitables for execution and waits for all of them to complete. This concurrent execution can improve the overall performance of your code by utilizing available system resources efficiently.
asyncio.run(): asyncio.run()
is a function provided by the asyncio
module in Python. It is used to run an asynchronous function or coroutine and manage the event loop for you. It is simply used to call the coroutine function. You canât call the async function so you need this function.
Flow execution
The code begins by importing the asyncio
module, which provides tools for asynchronous programming.
Two asynchronous functions, task1()
and task2()
, are defined. These functions represent individual tasks that will be executed concurrently.
The main()
function is defined as an asynchronous function. It serves as the entry point for running the asynchronous code.
Inside the main()
function, the asyncio.gather()
function is used to concurrently execute task1()
and task2()
and gather their results. The await
keyword is used to wait for the completion of the gather()
function, indicating that the program should pause until the tasks are finished.
The results of the tasks are stored in the results
variable. In this case, since there are two tasks, the results
variable will contain the results of both task1()
and task2()
.
The individual results are accessed by unpacking the results
variable into separate variables, result1
and result2
. This allows you to access and process the specific results of each task.
Finally, the event loop is run using asyncio.run(main())
. This executes the main()
function, which triggers the execution of the tasks specified in asyncio.gather()
.
if __name__ == "__main__":
arguments = get_args()
if not path.exists(arguments.wordlist):
print(colored(
"[-] Wordlist location is not right,\n[-] Provide the right path of the wordlist", 'red'))
exit(1)
print("\n---------------------------------------------------------\n---------------------------------------------------------")
print(colored(f"[*] Target\t: ", "light_red",), end="")
print(arguments.target)
print(colored(f"[*] Username\t: ", "light_red",), end="")
print(arguments.username)
print(colored(
f"[*] Port\t: ", "light_red"), end="")
print(arguments.port)
print(
colored(f"[*] Wordlist\t: ", "light_red"), end="")
print(arguments.wordlist)
print(colored(f"[*] Protocol\t: ", "light_red"), end="")
print("FTP")
print("---------------------------------------------------------\n---------------------------------------------------------", )
print(colored(
f"FTP-Bruteforce starting at {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}", 'yellow'))
print("---------------------------------------------------------\n---------------------------------------------------------")
asyncio.run(main(arguments.target, arguments.port,
arguments.username, arguments.wordlist))
Letâs understand this code[/color]
Ques: What is __name__==__main__
?
Answer: The __name__
variable in Python holds the name of the current namespace or module. Each module has its own namespace, which acts as a container for the variables, functions, and classes defined within that module.
In this case the code block inside if __name__ == "__main__":
will only execute if the current script is being run as the main program. It will not execute if the script is imported as a module by another script.
- The next line
arguments = get_args()
is calling a function get_args()
to retrieve the command-line arguments provided when running the script. It assigns the returned values to the arguments
variable.
- The following block of code checks whether the file specified in
arguments.wordlist
exists using the path.exists()
function from the path
module. If the file does not exist, it prints an error message in red color indicating that the wordlist location is not correct and exits the script with an exit code of 1.
- The next lines print out various information related to the FTP brute-forcing process. Each line uses the
print()
function to output a formatted string to the console.
- The lines starting with
print(colored(f"[*] Target\t: ", "light_red",), end="")
print the target information.
- Similarly, the lines starting with
print(colored(f"[*] Username\t: ", "light_red",), end="")
print the username information, and the line print(arguments.username)
prints the value of arguments.username
.
- The line
print(arguments.port)
prints the port number.
- - The line
print(arguments.wordlist)
prints the value of arguments.wordlist
.
- - The line and line after the line
print(colored(f"[*] Protocol\t: ", "light_red"), end="")
prints the protocol information, which is always âFTPâ.
- After printing the necessary information, a separator line is printed using
print("---------------------------------------------------------\n---------------------------------------------------------")
.
- The next few lines print a timestamped message indicating the start of the ftp brute-forcing process.
- The line
print(colored(f"FTP-Bruteforce starting at {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}", 'yellow'))
prints the message in yellow color, displaying the current date and time obtained from datetime.now()
in the specified format using strftime()
.
- Then there is another separator line
- In the last line of the code
asyncio.run(main(arguments.target, arguments.port, arguments.username, arguments.wordlist))
, we instructs the interpreter to run coroutine named main with arguments like target, port, username, wordlist.
đ Coding Out the main function
Now when our program is executed, the execution goes to our main function.
async def main(hostname, port, username, wordlist):
  tasks = []
  passwords = []
  found_flag = asyncio.Event()
  concurrency_limit = 10
  counter = 0
  with open(wordlist, 'r') as f:
    for password in f.readlines():
      password = password.strip()
      passwords.append(password)
  for password in passwords:
    if counter >= concurrency_limit:
      await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
      tasks = []
      counter = 0
if not found_flag.set():
tasks.append(asyncio.create_task(ftp_bruteforce(
hostname, username, password, port, found_flag)))
    await asyncio.sleep(1) # change the value according to the server
    counter += 1
Firstly we defined a corroutine function named main which takes various parameters like hostname, port, username, and wordlist and gets its value from the function calling.
The function starts by initializing some variables:
tasks
: A list to store the tasks (coroutines) for FTP brute-forcing.
passwords
: A list to store the passwords read from the wordlist file.
found_flag
: An asyncio.Event()
object that acts as a flag to indicate whether the correct password has been found.
In the asyncio module of Python, asyncio.Event
is a class that represents a simple synchronization primitive used for communication between coroutines or tasks. An event is a boolean state that is initially unset (False) and can be set (True) by calling the set()
method.
Here are the main methods provided by asyncio.Event
:
set()
: Sets the event, changing its state to True. Any coroutines or tasks waiting for the event to be set will be unblocked and can continue their execution.
clear()
: Resets the event, changing its state to False.
is_set()
: Returns True if the event is set, and False otherwise.
wait()
: Suspends the coroutine or task until the event is set. If the event is already set, this method returns immediately.
concurrency_limit
: An integer that determines the maximum number of concurrent tasks allowed
counter
: A counter variable to keep track of the number of concurrent tasks.
After declaring the variables, The function then opens the specified wordlist
file in read mode using a with
statement.
It reads each line of the wordlist file using f.readlines()
, and for each line, it strips any leading or trailing whitespace and appends the password to the passwords
list.
Next, a loop iterates over the passwords
list to initiate the ftp brute-forcing process:
If the counter
variable reaches the concurrency_limit
, it waits for the current batch of tasks to complete before proceeding. This is achieved using await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
. It waits until at least one task completes.
> The return_when
parameter accepts different values, including asyncio.FIRST_COMPLETED
, asyncio.FIRST_EXCEPTION
, asyncio.ALL_COMPLETED
, or an integer representing the number of tasks that need to complete. When return_when
is set to asyncio.FIRST_COMPLETED
, the wait()
function will return as soon as at least one of the awaited tasks has been completed. It does not wait for all tasks to finish before returning
After waiting, the tasks
list is cleared, and the counter
is reset to 0 to prepare for the next batch of concurrent tasks.
If the found_flag
is not set (indicating the correct password has not been found), a new task is created using asyncio.create_task(ftp_bruteforce(...))
. The ftp_bruteforce()
coroutine is invoked with the provided parameters (hostname, username, password, port, and found_flag) to attempt an ftp connection with the given password.
Then we introduce a small delay of 1 second using await asyncio.sleep(1)
. This allows for time slices in the event loop, preventing excessive CPU usage and allowing other tasks to run, and gives some time for the ftp server to react. You should the value of the delay according to your needs.
The counter
is incremented to keep track of the number of concurrent tasks.
After the loop, await asyncio.gather(*tasks)
is used to wait for all the remaining tasks to complete. It collects and waits for the results of all the tasks in the tasks
list.
If the found_flag
is not set after all tasks are complete, it indicates that the correct password was not found. In this case, a message is printed in red color using the print()
function and the colored()
function.
Coding the the ftp_bruteforce
function
This is the function responsible for testing the password, associated with a specific username and IP address.
async def ftp_bruteforce(hostname, username, password, port, found_flag):
try:
async with aioftp.Client.context(hostname, user=username, password=password, port=port) as client:
found_flag.set()
print(colored(
f"[{port}] [ftp] host:{hostname} login:{username} password:{password}", 'green'))
except Exception as err:
print(
f"[Attempt] target {hostname} - login:{username} - password:{password}")
- Firstly we define the coroutine named
ftp_bruteforce
which takes various parameters like, hostname username, password, port, and found_flag variable, function gets value from the main coroutine function.
- Inside the function , an FTP connection is attempted using the
aioftp.Client.context()
function. This function establishes an FTP connection to the specified hostname with the provided username and password. The async with
statement is used to ensure proper handling of the connection resources.
The aioftp
library is an asynchronous FTP (File Transfer Protocol) client implementation for Python. It is specifically designed to work with FTP servers and provides an intuitive high-level API that simplifies the process of interacting with FTP servers asynchronously. Through the use of the asyncio
framework, the library allows developers to perform tasks such as file uploads, downloads, directory listings, and other FTP operations concurrently without blocking the execution of other tasks.
The aioftp library works really well with the asyncio library.
The aioftp.client
module in the aioftp
library provides the client-side functionality for interacting with FTP servers asynchronously.
The code uses the aioftp.Client.context()
method as an asynchronous context manager to establish a connection to the FTP server. It passes the FTP serverâs hostname
, username
, password
, and port
as arguments.
The context manager provides a number of features that can help to simplify and improve the performance of FTP operations, including:
- Automatic connection pooling:: The context manager will automatically pool connections to the FTP server, which can significantly improve performance for long-running operations.
- Automatic cancellation: If the context manager is cancelled, any outstanding FTP operations will be cancelled as well.
- Automatic cleanup:Â When the context manager is closed, any resources that were allocated by the context manager will be released.
- If the connection is successfully established, the code block within the
async with
statement is executed. It sets the found_flag
using found_flag.set()
to indicate that the correct password has been found. It then prints a colored message to the console using colored()
indicating the successful connection attempt.
- 4. If an exception occurs during the FTP connection attempt, the code within the
except
block is executed. The exception is caught and stored in the err
variable. It prints a message to the console indicating the attempted connection with the target hostname
, username
, and password
. If something error happens at your end, you can check the error by printing this err
variable.
âDisclaimer
Note: The following disclaimer is intended for educational and ethical use of the FTP-brute force script.
This FTP brute-force script is solely intended for educational and testing purposes. It must not be used to gain unauthorized access to FTP servers or engage in any illegal activities. Any misuse or damage resulting from the use of this script is the sole responsibility of the user.