# NetFS 2 (misc)  
Writeup by: [xlr8or](https://ctftime.org/team/235001)

**Note:** This writeup assumes you are familiar with the solution to the NetFS
1 challenge.

This time we get a very similar server script, but it is slightly updated in
the authentication function where the password is checked:  
```python  
with Timeout(5) as timer:  
# Receive password  
self.response(b"Password: ")  
i = 0  
while i < len(password):  
c = self._conn.recv(1)  
if c == b'':  
timer.wait()  
self.response(b"Incorrect password.\n")  
return  
elif c != password[i:i+1]:  
timer.wait()  
self.response(b"Incorrect password.\n")  
return  
i += 1

if self._conn.recv(1) != b'\n':  
timer.wait()  
self.response(b"Incorrect password.\n")  
return

self.response(b"Logged in.\n")  
self._auth = True  
self._user = username  
```

The password is still checked one character at a time, however we are not
immediately notified when the character is incorrect, rather the timer waits
for a similar amount of time, as timing out would, if the single character we
sent was correct.

Let's see how the `timer.wait()` function is implemented:  
```python  
def wait(self):  
signal.alarm(0)  
while time.time() - self.start < self.seconds:  
time.sleep(0.1)  
time.sleep(random.random())  
```

Essentially, this will cancel the signal countdown for the alarm signal that
was set, when the `Timer` was "entered" in the `with` block.  
Then wait for the remaining time, and delay for a random amount of time.

When the execution goes outside the `with` statement the `__exit__` handler
gets called:  
```python  
def __exit__(self, _type, _value, _traceback):  
signal.alarm(0)  
time.sleep(random.random())  
```

This will again sleep for a random amount of time.  
However, when the password character is correct, we don't call `timer.wait()`,
so we have a situation, where an incorrect character would sleep a random
amount of time twice (because calling `timer.wait()`), but a correct character
would only sleep the random amount of time once.

I spent some time analysing this route, trying to see if I can deduce the
correct character from the timings.  
Unfortunately I couldn't manage to find a way to deduce the correct character
from the timing, in fact all methods I have tried were more likely to report
an incorrect character as the best potential guess.

At this point I started inspecting the traffic in Wireshark, as I was trying
to see if we can somehow detect, that when the password character is
incorrect, the program waits, then exits, and never calls `socket.recv(1)`
again.  
Although I saw, that all packets are acknowledged, even after sending an
incorrect password character, I noticed something interesting.

When sending an incorrect password character, the server would send an `RST`
packet, and netcat would display `read(net): Connection reset by peer`, while
sending the correct password character, would produce no such message in
netcat and network traffic would not contain any `RST` packets.

This is also true for the remote, with a slight change, when the password
character is correct, there's an `RST` packet, however it is sent by the
client to the server.  
More important is that netcat shows the same behavior with regards to the
error message on the remote as well.  
Using this knowledge, we have an oracle for a single password character at a
time again, so we can write a script that can recover the `admin` password.

`solve.py` is going to recover the `admin` password, which can then be used to
read the flag file.

The flag hints at using `procfs` as an oracle, so it is possible that there
are multiple solutions to this challenge, because indeed, except for the
blacklisted words in `PROTECTED` we have arbitrary file read with the `guest`
user.  

Original writeup
(https://github.com/sbencoding/zer0pts_ctf_2023_writeups/tree/main/misc/netfs2).