moved tests between the 'extra' and 'malicious' category, and added a new check for whether or not malicious tests should be run

This commit is contained in:
cwshugg 2022-03-29 08:45:31 -04:00
parent 83ef85d764
commit 5555adff24

View File

@ -468,6 +468,12 @@ class Single_Conn_Malicious_Case(Doc_Print_Test_Case):
self.username = 'user0' self.username = 'user0'
self.password = 'thepassword' self.password = 'thepassword'
# Prepare the a_string for query checks
self.a_string = "aaaaaaaaaaaaaaaa"
for x in range(0, 6):
self.a_string = self.a_string + self.a_string
def setUp(self): def setUp(self):
""" Test Name: None -- setUp function\n\ """ Test Name: None -- setUp function\n\
Number Connections: N/A \n\ Number Connections: N/A \n\
@ -490,7 +496,49 @@ class Single_Conn_Malicious_Case(Doc_Print_Test_Case):
# Close the requests session # Close the requests session
self.session.close() self.session.close()
def test_method_check_long(self):
""" Test Name: test_method_check_4\n\
Number Connections: 1 \n\
Procedure: Test a request using a long method:\n\
aa....aaa /api/login HTTP/1.1
"""
http_connection = HTTPConnection(hostname, port)
run_method_check(http_connection, self.a_string*2, self.hostname)
http_connection.close()
def test_method_check_4(self):
""" Test Name: test_method_check_4\n\
Number Connections: 1 \n\
Procedure: Test a request using a different method than GET:\n\
ASD /api/login HTTP/1.1
"""
http_connection = HTTPConnection(hostname, port)
run_method_check(http_connection, "ASD", self.hostname)
http_connection.close()
def test_login_post_invalid_body(self):
""" Test Name: test_login_post_invalid_body\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an ill-formed body i.e. not JSON
for /api/login. Not checking for response text.
"""
http_connection = HTTPConnection(hostname, port)
# Ill-formed body for the request
data = '"username": "%s", "password": "%s"' % (self.username, self.password)
# POST request for login
http_connection.request("POST", "/api/login", data)
# Get the server response
server_response = http_connection.getresponse()
# Check the response code (403 or 400)
self.assertTrue(server_response.status == FORBIDDEN or
server_response.status == BAD_REQUEST)
http_connection.close()
def test_multi_connection_disconnect(self): def test_multi_connection_disconnect(self):
""" Test Name: test_multi_connection_disconnect\n\ """ Test Name: test_multi_connection_disconnect\n\
@ -722,6 +770,253 @@ class Single_Conn_Malicious_Case(Doc_Print_Test_Case):
sock.close() sock.close()
def test_auth_browser_cookies(self):
""" Test Name: test_auth_browser_cookies
Number Connections: N/A
Procedure: Sends cookies that are actually taken from one a web browser
when connecting to courses.cs.vt.edu. They're valid cookies, but they
should not be recognized by the server as valid authentication.
Similar idea for test_auth_wrong_cookie. A failure here means the server
might have crashed or it served a private file despite without checking
for valid authentication.
"""
# set up a few different cookies to try (no, these don't actually work
# - don't try any session hijacking with these cookies ;), we made
# sure they are invalid)
cookies = [
["IDMSESSID", "9DD957C450BBCFE9D75022A05DC71D0E701FE23AF0DEE777090831C9FFD087FF0EE5704771BA11D02B3FA5CC13F20B4F8A6758A02768E160AE1E100A8D4BECCE"],
["auth_token", "[\"hokiebird\"\054 \"Hokie Bird\"].Yhy5-g.HXxh5WxmTawBv_LHPaTLnXNkYiI|5b9df2848955b572910a6ff3d2c98d27febbe6a8949c18cde52c8c11c91ed5437f40accae8f8b77a41e335e83556a3670d5f5178d8ddd4f8eb83e1a82974ce4a"],
["session", ".eJwlzsFKw0AQgOFXKXuuZXczm93psV4qFBEs2GAkzM7OJEVNIaG2IL67hV7_e_L9mk4nmQezVvqaZWm6YzFrQ947cJoLRslQAaDW3hlSql2JZKUUT5m9S1LFCEKRMaqVKFqSg0hellofmStWUATRANYGmzmRdRA4E6KU2musEFXZ5aBY2hiyoGRukPMs013z3hq-zMO571uzXLTm8TSOp2xxei8fq2ZowkO_2h6uQ3i7fu_plvnpdtsX2u_Gw_Nnc3zyf_8CpUfl.Yjo3NQ.m-n22sd9bMNXyvtXpIS6dZ85Cv4"]
]
# now, come up with various combinations of cookies to try
cookie_combos = []
for c1 in cookies:
combo = []
combo.append(c1)
cookie_combos.append([c1])
for c2 in cookies:
if c1 != c2:
combo.append(c2)
cookie_combos.append(combo)
# loop through each of the cookies
for combo in cookie_combos:
# clear the session cookies and set cookies
self.session.cookies.clear()
for cookie in combo:
self.session.cookies.set(cookie[0], cookie[1])
# try making a GET /api/login request
response = None
try:
response = self.session.get('http://%s:%s/api/login' % (self.hostname, self.port), timeout=2)
except requests.exception.RequestException:
raise AssertionError("The server did not respond within 2s")
# make sure the correct response code was sent
if response.status_code != requests.codes.ok:
raise AssertionError("The server responded with %d instead of 200 OK for a GET /api/login request" %
response.status_code)
# make sure the JSON data returned is empty
if response.text.strip() != "{}":
raise AssertionError("The server returned something other than an empty JSON object ({}) for a "
"GET /api/login request with invalid cookies. Received: '%s'" % response.text)
# now, try making a request for a private file
response = None
try:
response = self.session.get('http://%s:%s/private/secure.html' % (self.hostname, self.port), timeout=2)
except requests.exception.RequestException:
raise AssertionError("The server did not respond within 2s")
# make sure we didn't receive a 200 OK
if response.status_code == requests.codes.ok:
raise AssertionError("The server served a private file despite not being authenticated.")
##############################################################################
## Class: Single_Conn_Bad_Case
## Test cases that aim for various errors in well-formed queries.
##############################################################################
class Single_Conn_Bad_Case(Doc_Print_Test_Case):
"""
Test case for a single connection, using bad requests that are
well formed. The tests are aptly named for describing their effects.
Each case should be handled gracefully and without the server crashing.
"""
def __init__(self, testname, hostname, port):
"""
Prepare the test case for creating connections.
"""
super(Single_Conn_Bad_Case, self).__init__(testname)
self.hostname = hostname
self.port = port
N = 10
self.username = 'user0'
self.invalid_username = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for _ in range(N))
self.password = 'thepassword'
self.invalid_password = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for _ in range(N))
self.private_file = 'private/secure.html'
def setUp(self):
""" Test Name: None -- setUp function\n\
Number Connections: N/A \n\
Procedure: Opens the HTTP connection to the server. An error here \
means the script was unable to create a connection to the \
server.
"""
# make a 'session' object (used by some of the tests that were moved
# here)
self.session = requests.Session()
# Make HTTP connection for the server
self.http_connection = HTTPConnection(self.hostname, self.port)
# Connect to the server
self.http_connection.auto_open = 0
self.http_connection.connect()
def tearDown(self):
""" Test Name: None -- tearDown function\n\
Number Connections: N/A \n\
Procedure: Closes the HTTP connection to the server. An error here \
means the server crashed after servicing the request from \
the previous test.
"""
# Close the HTTP connection
self.http_connection.close()
if server.poll() is not None:
# self.fail("The server has crashed. Please investigate.")
print("The server has crashed. Please investigate.")
def test_404_not_found_1(self):
""" Test Name: test_404_not_found_1\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /junk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/junk", self.hostname)
def test_404_not_found_2(self):
""" Test Name: test_404_not_found_2\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/login/api/login HTTP/1.1
"""
run_404_check(self.http_connection, "/api/login/api/login", self.hostname)
def test_404_not_found_3(self):
""" Test Name: test_404_not_found_3\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/logon HTTP/1.1
"""
run_404_check(self.http_connection, "/api/logon", self.hostname)
def test_404_not_found_4(self):
""" Test Name: test_404_not_found_4\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/api/login HTTP/1.1
"""
run_404_check(self.http_connection, "/api/api/login", self.hostname)
def test_404_not_found_5(self):
""" Test Name: test_404_not_found_5\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/loginjunk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/api", self.hostname)
def test_404_not_found_6(self):
""" Test Name: test_404_not_found_6\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/loginjunk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/loginjunk", self.hostname)
def test_404_not_found_7(self):
""" Test Name: test_404_not_found_7\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /login/api HTTP/1.1
"""
run_404_check(self.http_connection, "/login/api", self.hostname)
def test_login_post_invalid_username(self):
""" Test Name: test_login_post_invalid_username\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an incorrect username using a
well-formed request for /api/login. Not checking for response text.
"""
# JSON body for the request
data = {"username": self.invalid_username, "password": self.password}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, FORBIDDEN)
def test_login_post_invalid_password(self):
""" Test Name: test_login_post_invalid_password\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an incorrect password using a
well-formed request for /api/login. Not checking for response text.
"""
# JSON body for the request
data = {"username": self.username, "password": self.invalid_password}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, FORBIDDEN)
def test_login_valid_body_extra_parameters(self):
""" Test Name: test_login_valid_body_extra_parameters\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing a well-formed body with extra
parameters in the JSON body for /api/login. Not checking for
response text.
"""
# JSON body for the request
data = {"username": self.username, "password": self.password, "key": "value"}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, OK)
def test_auth_flipped_token(self): def test_auth_flipped_token(self):
""" Test Name: test_auth_flipped_token """ Test Name: test_auth_flipped_token
Number Connections: N/A Number Connections: N/A
@ -859,291 +1154,6 @@ class Single_Conn_Malicious_Case(Doc_Print_Test_Case):
self.assertEqual(response.status_code, requests.codes.ok, self.assertEqual(response.status_code, requests.codes.ok,
"Server failed to respond with private file despite being authenticated.") "Server failed to respond with private file despite being authenticated.")
def test_auth_wrong_cookie2(self):
""" Test Name: test_auth_wrong_cookie2
Number Connections: N/A
Procedure: Sends cookies that are actually taken from one a web browser
when connecting to courses.cs.vt.edu. They're valid cookies, but they
should not be recognized by the server as valid authentication.
Similar idea for test_auth_wrong_cookie. A failure here means the server
might have crashed or it served a private file despite without checking
for valid authentication.
"""
# set up a few different cookies to try (no, these don't actually work
# - don't try any session hijacking with these cookies ;), we made
# sure they are invalid)
cookies = [
["IDMSESSID", "9DD957C450BBCFE9D75022A05DC71D0E701FE23AF0DEE777090831C9FFD087FF0EE5704771BA11D02B3FA5CC13F20B4F8A6758A02768E160AE1E100A8D4BECCE"],
["auth_token", "[\"hokiebird\"\054 \"Hokie Bird\"].Yhy5-g.HXxh5WxmTawBv_LHPaTLnXNkYiI|5b9df2848955b572910a6ff3d2c98d27febbe6a8949c18cde52c8c11c91ed5437f40accae8f8b77a41e335e83556a3670d5f5178d8ddd4f8eb83e1a82974ce4a"],
["session", ".eJwlzsFKw0AQgOFXKXuuZXczm93psV4qFBEs2GAkzM7OJEVNIaG2IL67hV7_e_L9mk4nmQezVvqaZWm6YzFrQ947cJoLRslQAaDW3hlSql2JZKUUT5m9S1LFCEKRMaqVKFqSg0hellofmStWUATRANYGmzmRdRA4E6KU2musEFXZ5aBY2hiyoGRukPMs013z3hq-zMO571uzXLTm8TSOp2xxei8fq2ZowkO_2h6uQ3i7fu_plvnpdtsX2u_Gw_Nnc3zyf_8CpUfl.Yjo3NQ.m-n22sd9bMNXyvtXpIS6dZ85Cv4"]
]
# now, come up with various combinations of cookies to try
cookie_combos = []
for c1 in cookies:
combo = []
combo.append(c1)
cookie_combos.append([c1])
for c2 in cookies:
if c1 != c2:
combo.append(c2)
cookie_combos.append(combo)
# loop through each of the cookies
for combo in cookie_combos:
# clear the session cookies and set cookies
self.session.cookies.clear()
for cookie in combo:
self.session.cookies.set(cookie[0], cookie[1])
# try making a GET /api/login request
response = None
try:
response = self.session.get('http://%s:%s/api/login' % (self.hostname, self.port), timeout=2)
except requests.exception.RequestException:
raise AssertionError("The server did not respond within 2s")
# make sure the correct response code was sent
if response.status_code != requests.codes.ok:
raise AssertionError("The server responded with %d instead of 200 OK for a GET /api/login request" %
response.status_code)
# make sure the JSON data returned is empty
if response.text.strip() != "{}":
raise AssertionError("The server returned something other than an empty JSON object ({}) for a "
"GET /api/login request with invalid cookies. Received: '%s'" % response.text)
# now, try making a request for a private file
response = None
try:
response = self.session.get('http://%s:%s/private/secure.html' % (self.hostname, self.port), timeout=2)
except requests.exception.RequestException:
raise AssertionError("The server did not respond within 2s")
# make sure we didn't receive a 200 OK
if response.status_code == requests.codes.ok:
raise AssertionError("The server served a private file despite not being authenticated.")
##############################################################################
## Class: Single_Conn_Bad_Case
## Test cases that aim for various errors in well-formed queries.
##############################################################################
class Single_Conn_Bad_Case(Doc_Print_Test_Case):
"""
Test case for a single connection, using bad requests that are
well formed. The tests are aptly named for describing their effects.
Each case should be handled gracefully and without the server crashing.
"""
def __init__(self, testname, hostname, port):
"""
Prepare the test case for creating connections.
"""
super(Single_Conn_Bad_Case, self).__init__(testname)
self.hostname = hostname
self.port = port
# Prepare the a_string for query checks
self.a_string = "aaaaaaaaaaaaaaaa"
for x in range(0, 6):
self.a_string = self.a_string + self.a_string
N = 10
self.username = 'user0'
self.invalid_username = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for _ in range(N))
self.password = 'thepassword'
self.invalid_password = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase) for _ in range(N))
def setUp(self):
""" Test Name: None -- setUp function\n\
Number Connections: N/A \n\
Procedure: Opens the HTTP connection to the server. An error here \
means the script was unable to create a connection to the \
server.
"""
# Make HTTP connection for the server
self.http_connection = HTTPConnection(self.hostname, self.port)
# Connect to the server
self.http_connection.auto_open = 0
self.http_connection.connect()
def tearDown(self):
""" Test Name: None -- tearDown function\n\
Number Connections: N/A \n\
Procedure: Closes the HTTP connection to the server. An error here \
means the server crashed after servicing the request from \
the previous test.
"""
# Close the HTTP connection
self.http_connection.close()
if server.poll() is not None:
# self.fail("The server has crashed. Please investigate.")
print("The server has crashed. Please investigate.")
def test_404_not_found_1(self):
""" Test Name: test_404_not_found_1\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /junk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/junk", self.hostname)
def test_404_not_found_2(self):
""" Test Name: test_404_not_found_2\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/login/api/login HTTP/1.1
"""
run_404_check(self.http_connection, "/api/login/api/login", self.hostname)
def test_404_not_found_3(self):
""" Test Name: test_404_not_found_3\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/logon HTTP/1.1
"""
run_404_check(self.http_connection, "/api/logon", self.hostname)
def test_404_not_found_4(self):
""" Test Name: test_404_not_found_4\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/api/login HTTP/1.1
"""
run_404_check(self.http_connection, "/api/api/login", self.hostname)
def test_404_not_found_5(self):
""" Test Name: test_404_not_found_5\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/loginjunk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/api", self.hostname)
def test_404_not_found_6(self):
""" Test Name: test_404_not_found_6\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /api/loginjunk HTTP/1.1
"""
run_404_check(self.http_connection, "/api/loginjunk", self.hostname)
def test_404_not_found_7(self):
""" Test Name: test_404_not_found_7\n\
Number Connections: 1 \n\
Procedure: Test a simple GET request for an illegal object URL:\n\
GET /login/api HTTP/1.1
"""
run_404_check(self.http_connection, "/login/api", self.hostname)
def test_method_check_long(self):
""" Test Name: test_method_check_4\n\
Number Connections: 1 \n\
Procedure: Test a request using a long method:\n\
aa....aaa /api/login HTTP/1.1
"""
run_method_check(self.http_connection, self.a_string*2, self.hostname)
def test_method_check_4(self):
""" Test Name: test_method_check_4\n\
Number Connections: 1 \n\
Procedure: Test a request using a different method than GET:\n\
ASD /api/login HTTP/1.1
"""
run_method_check(self.http_connection, "ASD", self.hostname)
def test_login_post_invalid_username(self):
""" Test Name: test_login_post_invalid_username\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an incorrect username using a
well-formed request for /api/login. Not checking for response text.
"""
# JSON body for the request
data = {"username": self.invalid_username, "password": self.password}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, FORBIDDEN)
def test_login_post_invalid_password(self):
""" Test Name: test_login_post_invalid_password\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an incorrect password using a
well-formed request for /api/login. Not checking for response text.
"""
# JSON body for the request
data = {"username": self.username, "password": self.invalid_password}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, FORBIDDEN)
def test_login_post_invalid_body(self):
""" Test Name: test_login_post_invalid_body\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing an ill-formed body i.e. not JSON
for /api/login. Not checking for response text.
"""
# Ill-formed body for the request
data = '"username": "%s", "password": "%s"' % (self.username, self.password)
# POST request for login
self.http_connection.request("POST", "/api/login", data)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code (403 or 400)
self.assertTrue(server_response.status == FORBIDDEN or
server_response.status == BAD_REQUEST)
def test_login_valid_body_extra_parameters(self):
""" Test Name: test_login_valid_body_extra_parameters\n
Number Connections: One \n\
Procedure: Simple POST request:\n\
POST /api/login HTTP/1.1
Run a check for login by providing a well-formed body with extra
parameters in the JSON body for /api/login. Not checking for
response text.
"""
# JSON body for the request
data = {"username": self.username, "password": self.password, "key": "value"}
body = json.dumps(data)
# POST request for login
self.http_connection.request("POST", "/api/login", body)
# Get the server response
server_response = self.http_connection.getresponse()
# Check the response code
self.assertEqual(server_response.status, OK)
class Multi_Conn_Sequential_Case(Doc_Print_Test_Case): class Multi_Conn_Sequential_Case(Doc_Print_Test_Case):
""" """
@ -2919,14 +2929,24 @@ process.
# Check if the server passed the extra tests # Check if the server passed the extra tests
if test_results.wasSuccessful(): if test_results.wasSuccessful():
print("\nYou have passed the Extra Tests for this project!\n") print("\nYou have passed the Extra Tests for this project!\n")
else:
print("\nYou have NOT passed the Extra Tests for this project.\n" + # decide whether or not we should run the malicious tests
"Please examine the above errors, the Malicious Tests\n" + do_run_malicious = minreq_score == 1.0 and \
"will not be run until the above tests pass.\n") auth_score == 1.0 and \
extra_score == 1.0
if not do_run_malicious:
print("\nYou have NOT passed one of the following test categories:\n"
" - %s\n - %s\n - %s\n"
"Please examine the errors above. The Malicious tests will not\n"
"be run until the above tests pass.\n" %
(test_categories["minreq"]["name"],
test_categories["auth"]["name"],
test_categories["extra"]["name"]))
print_points(minreq_score, extra_score, 0, ipv6_score, auth_score, fallback_score, video_score) print_points(minreq_score, extra_score, 0, ipv6_score, auth_score, fallback_score, video_score)
sys.exit() sys.exit()
print("Now running the MALICIOUS Tests. WARNING: These tests will not necessarily run fast!") print("Now running the MALICIOUS Tests. WARNING: These tests will not necessarily run fast!")
time.sleep(1) time.sleep(1)
# Run the malicious tests # Run the malicious tests