Paper 2 Network Security Answers
These answers correspond to Paper 2 Network Security Drills.
Verification note: every Python code block in this answer file has been executed locally.
Answer 1: Password Validator
Model answer:
def valid_password(password):
if len(password) < 8:
return False
has_digit = False
for ch in password:
if ch.isdigit():
has_digit = True
return has_digit
print(valid_password("abc123"))
print(valid_password("password"))
print(valid_password("secure9x"))Expected output:
False
False
TrueMark points:
- defines a function that accepts the password;
- checks the minimum length condition;
- checks each character or uses an equivalent digit test;
- detects at least one digit;
- returns
Falsewhen the password is too short; - returns
Falsewhen the password has no digit; - returns
Trueonly when both conditions are met.
Common weak answer:
- using
password.isdigit(), which only checks whether the whole password consists of digits.
Answer 2: Login Attempts
Model answer:
def simulate_login_attempts(correct_password, attempts):
failed = 0
locked = False
results = []
for attempt in attempts:
if locked:
results.append("LOCKED")
elif attempt == correct_password:
results.append("SUCCESS")
failed = 0
else:
failed = failed + 1
results.append("FAIL")
if failed == 3:
locked = True
return results
print(simulate_login_attempts("open123", ["bad", "wrong", "nope", "open123"]))Expected output:
['FAIL', 'FAIL', 'FAIL', 'LOCKED']Mark points:
- initializes the failed-attempt counter;
- initializes or represents the locked state;
- loops through attempts in order;
- checks locked state before checking the password;
- appends
"LOCKED"for attempts after lockout; - resets the counter after a successful login before lockout;
- increments the counter for wrong passwords;
- locks the account once the counter reaches three and matches the expected result list.
Common weak answer:
- allowing the correct fourth attempt after three failures. The question states the account is locked for all later attempts.
Answer 3: Access Log Filter
Model answer:
logs = [
{"user": "amy", "event": "LOGIN_OK", "ip": "192.0.2.10"},
{"user": "bo", "event": "LOGIN_FAIL", "ip": "192.0.2.11"},
{"user": "cy", "event": "LOGIN_FAIL", "ip": "192.0.2.11"},
{"user": "amy", "event": "LOGOUT", "ip": "192.0.2.10"}
]
def failed_ips(logs):
ips = []
for record in logs:
if record["event"] == "LOGIN_FAIL":
ips.append(record["ip"])
return ips
print(failed_ips(logs))Expected output:
['192.0.2.11', '192.0.2.11']Mark points:
- loops through the log records;
- checks the
eventfield; - selects only
LOGIN_FAILrecords; - extracts the
ipfield; - preserves order;
- returns the exact list shown.
Common weak answer:
- returning usernames instead of IP addresses. The question asks for failed-login IPs.
Answer 4: Simple Hash Check
This constructed "hash:" + entered_password value is only a teaching simulation. It is not a secure hash algorithm.
Model answer:
stored_hash = "hash:letmein"
def authenticate(entered_password, stored_hash):
entered_hash = "hash:" + entered_password
return entered_hash == stored_hash
print(authenticate("letmein", stored_hash))
print(authenticate("guess", stored_hash))Expected output:
True
FalseMark points:
- creates a hash value from the entered password using the specified method;
- compares hash strings rather than comparing against plaintext storage;
- returns
Truewhen the generated hash matches; - returns
Falsewhen it does not match; - produces both expected outputs.
Common weak answer:
- storing and comparing the plaintext password. The exercise is about checking a derived stored value.
Answer 5: MFA Code
Model answer:
def mfa_login(password, code):
if password != "river7":
return "PASSWORD FAILED"
if code != "481926":
return "CODE FAILED"
return "ACCESS GRANTED"
print(mfa_login("river7", "481926"))
print(mfa_login("wrong", "481926"))
print(mfa_login("river7", "000000"))Expected output:
ACCESS GRANTED
PASSWORD FAILED
CODE FAILEDMark points:
- checks the password first;
- returns the password-failure message when the password is wrong;
- checks the one-time code only after the password is correct;
- returns the code-failure message when the password is correct but code is wrong;
- grants access only when both values match;
- produces the three expected outputs.
Common weak answer:
- granting access if either the password or code is correct. Multi-factor authentication requires both.
Answer 6: Firewall Rules
Model answer:
rules = [
{"port": 80, "action": "ALLOW"},
{"port": 443, "action": "ALLOW"},
{"port": 22, "action": "BLOCK"}
]
def check_packet(port):
for rule in rules:
if rule["port"] == port:
return rule["action"]
return "BLOCK"
print(check_packet(80))
print(check_packet(22))
print(check_packet(25))Expected output:
ALLOW
BLOCK
BLOCKMark points:
- represents each firewall rule with port and action data;
- loops through all rules;
- compares the packet port with the rule port;
- returns
ALLOWfor port80; - returns
BLOCKfor port22; - uses a default action when no rule matches;
- default action is
BLOCK; - produces all three expected outputs.
Common weak answer:
- allowing unknown ports by default. A conservative firewall rule set usually blocks traffic not explicitly allowed.
Answer 7: IDS Alert
Model answer:
requests = [
"203.0.113.5",
"203.0.113.8",
"203.0.113.8",
"203.0.113.8",
"198.51.100.2"
]
def flag_ips(requests, threshold):
counts = {}
for ip in requests:
if ip not in counts:
counts[ip] = 0
counts[ip] = counts[ip] + 1
flagged = []
for ip in counts:
if counts[ip] >= threshold:
flagged.append(ip)
return sorted(flagged)
print(flag_ips(requests, 3))Expected output:
['203.0.113.8']Mark points:
- initializes a frequency dictionary;
- loops through every request IP;
- creates a counter for a new IP;
- increments the count correctly;
- compares counts with the threshold;
- includes IPs whose count is greater than or equal to the threshold;
- sorts or otherwise returns the required deterministic list;
- matches the expected flagged IP list.
Common weak answer:
- flagging every IP that appears at least once. The threshold is part of the IDS rule.
Answer 8: Encryption Demonstration
Model answer:
def shift_encrypt(text, key):
result = ""
for ch in text:
if "A" <= ch <= "Z":
offset = ord(ch) - ord("A")
shifted = (offset + key) % 26
result = result + chr(ord("A") + shifted)
else:
result = result + ch
return result
print(shift_encrypt("ATTACK AT 9", 2))Expected output:
CVVCEM CV 9Mark points:
- loops through every character;
- identifies uppercase letters;
- converts a letter to an alphabet position;
- adds the key;
- wraps around using modulo 26;
- preserves non-uppercase characters unchanged;
- produces the expected ciphertext for the test.
Common weak answer:
- describing this as secure real-world encryption. It is only a simple teaching demonstration.
Answer 9: Signature Workflow
Model answer:
def simple_hash(message):
return str(sum(ord(ch) for ch in message))
def sign(message, private_key):
return simple_hash(message) + ":" + private_key
def verify(message, signature, public_key):
expected = simple_hash(message) + ":" + public_key
return signature == expected
sig = sign("submit marks", "teacher_key")
print(sig)
print(verify("submit marks", sig, "teacher_key"))
print(verify("change marks", sig, "teacher_key"))Expected output:
1234:teacher_key
True
FalseMark points:
- computes a hash-like value from the message;
- signs by combining the message hash with the private-key text as specified;
- verifies by recomputing the hash from the received message;
- combines the recomputed hash with the public-key text as specified;
- returns
Truefor the unchanged message and matching key; - returns
Falsefor the changed message.
Common weak answer:
- checking only the key text and not the message hash. A signature workflow should detect message changes.
Real digital signatures use a private key for signing and a corresponding public key for verification. The identical string in this task is only a simplified stand-in for a matching key pair.
Answer 10: Security Test Cases
Model answer:
def valid_password(password):
if len(password) < 8:
return False
has_digit = False
for ch in password:
if ch.isdigit():
has_digit = True
return has_digit
def run_password_tests():
tests = [
("abc123", False),
("abcdefgh", False),
("abcd1234", True)
]
results = []
for password, expected in tests:
result = valid_password(password)
results.append((password, result))
return results
print(run_password_tests())Expected output:
[('abc123', False), ('abcdefgh', False), ('abcd1234', True)]These results match the expected values in the test-case table: the first password is too short, the second has no digit, and the third satisfies both rules.
Mark points:
- includes the short-password test case;
- includes the no-digit test case;
- includes the valid-password test case;
- calls the validator for each test input;
- returns or prints the expected result pairs.
Common weak answer:
- listing test inputs without expected results. Good test cases state what should happen.