Listen wifi with wireshark
I always knew, that it is possible to catch wifi network packets. But haven't done it in practise (i was analysing network packets, but not in HTTP protocol). So i decided to do it, as this is interesting and useful. Such experience help to understand TCP-IP and HTTP protocols and also to pay more attention for web security.
We'll spy the network traffic with Wireshark program. There are a lot of tools for such purpose (ngrep, tcpdump, mitmproxy), but Wireshark looks the most popular and have a reach functionality.
Lets try to solve following tasks:
- listen network packets, that are sent/recieved inside local machine
- listen network packets, that are sent/recieved by local machine to/from outer world (internet)
- listen network packets, that are sent/recieved by other members of public wifi network
- listen network packets, that are sent/recieved by other members of private wifi network
All actions i performed on laptop MacBook Pro with OS X Yosemite, so on other devices there can be some differences.
Disclaimer: all actions are on your own responsibility. Do not apply described technics to make bad things.
Install wireshark. Launch it, go to Capture -> Intefaces.
My laptop is connected to wifi only (en0 interface). As i understand, awdl0 is a cable network. No cable is connected, so we don't see any packets. And lo0 is a localhost interface, lets work with it now.
Put checkbox at lo0 and press Start. To concentrate on HTTP protocol, set Display filter: http (this filter will be applied to already fetched and decoded packets, unlike Capture filter, which i'll describe later):
We are gonna catch packets, that will be sent from browser to django development server and back. There are some things about django server, that worth mentioning.
- First, it respond HTTP 1.0, not HTTP 1.1
- Second, the most important, response can not include
Content-Length: <response length>, neither
Transfer-Encoding: chunked. In that case, to determine the end of HTTP response we need to wait for server to close the connection. But it will not happen. HTTP data can be transmitted in several TCP segments and wireshark smart enough to group those segments and to show final HTTP response. But, as wireshark can't understand exactly when response is completed, it will not group them in HTTP frame.
Well, this is not very bad, as we can always look for response to particular request by clicking on it and apply Analyse -> Follow TCP Stream. But, it will be great to see the HTTP response in frame list.
To do it, prepend
ConditionalGetMiddleware to the list of
if DEBUG: MIDDLEWARE_CLASSES = ( 'django.middleware.http.ConditionalGetMiddleware', ) + MIDDLEWARE_CLASSES
Middleware will set Content-Length header.
It is not necessary, but useful in case of working with wireshark and django dev server. In other cases all work correctly: production servers usually set
Transfer-Encoding: chunked and respond HTTP by chunks (probably it is done by proxy server (nginx, apache, etc)).
Now start django project. The main page just shows the name of current user and login form. If current user is anonymous, then his name will be "AnonymousUser".
For the purity of the experiment clean cookies in browser for 127.0.0.1. Open page http://127.0.0.1:8000/.
If we have not add 'ConditionalGetMiddleware', then we'll probably see only request:
The response still can be found, if we choose Analyse->Follow TCP Stream:
And with 'ConditionalGetMiddleware' the response will be visible in list of frames:
Content-Length is set:
Well, it wasn't very interesting. But lets try to login!
Enter username and password and press Login. In wireshark we'll see 4 new frames: POST request, redirect to main page (302 code), GET / request, response for GET:
Look at fetched data more carefully.
Frame with POST request, along with HTTP headers contain form data. Here how they look like:
Login and password as plain text.
Response to POST request was 302 redirect. In that response server ask client to store session id in cookies:
Next goes GET request for main page, cookies now contain session id:
So this is how we can spy the network data, that is sent from client to server and back. All these information will be transmitted in wifi network by the same way (if non secure HTTP protocol is used). If we login - login and password are sent in plain text. If we just make a requst - our session id is visible.
With session id it is easy to access the site as the owner of that session. For simplicity, we can check that with console tool (curl, httpie). Example with httpie:
$ http 127.0.0.1:8000 "Cookie: sessionid=tmpocxkz6zsir6xe6i03kspucvlqq385" HTTP/1.0 200 OK Content-Length: 567 Content-Type: text/html; charset=utf-8 Date: Thu, 16 Apr 2015 13:06:58 GMT Server: WSGIServer/0.1 Python/2.7.6 Set-Cookie: csrftoken=3bUoLB28WyzcH7qG5GXreWPm0Pj11861; expires=Thu, 14-Apr-2016 13:06:58 GMT; Max-Age=31449600; Path=/ Vary: Cookie X-Frame-Options: SAMEORIGIN <html> <body> Hello, alex <div> <form action="/login/" method="POST"> <input type="text" name="username" /> <input type="password" name="password" /> <input type="text" hidden name="next" value="/" /> <input type='hidden' name='csrfmiddlewaretoken' value='3bUoLB28WyzcH7qG5GXreWPm0Pj11861' /> <input type="submit" name="Login" value="Login" /> </form> </div> </body> </html>
Hello, alex is shown, so the server treat us as alex.
Listen for network packets of our computer.
In wireshark choose Capture -> Intefaces, apply en0 interface and press Start:
I access the admin page of this site (lexev.org). In wireshark set Display filter
http.request.full_uri contains lexev.org
to see data flow only with host lexev.org. Here what we get:
We have now a session id of admin user.
So far we were listening just our request and responses. But lets try to listen for other people.
Go to cafe with public wifi, launch wireshark. Choose corresponding interface in Capture -> Intefaces and press Options (not Start).
We'll see something like this:
Double click on interface and put Capture packets in monitor mode checkbox:
Ok, Start. Now we are listening entire wifi network (excluding yourself).
In public network fly a lot of packets, we can easily fetch more than Gb information in hour. Hard to work with such amount of data, that is where Capture filters can help. Packets that are discarded by capture filters will not be saved. Unlike display filters, they are applied to not yet processed and decrypted packets, therefore it is harder to construct the filter.
Here is a capture filter for HTTP GET and POST requests only on 80 port:
(port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420) or (tcp dst port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354))
Apply it at interface Options:
Lets try to connect to wifi from another device (phone for example) and send GET request to lexev.org from it. For convenience add same display filter to show only requests to lexev.org (we can combine capture and display filters). Session id is visible again:
Just for fun, lets try to login. Look for my password everyone :):
For example we have a wifi with WPA protection. Choose corresponding interface, enable monitor mode for it (the same way as for public network, just without capture filter) and listen. We'll see something like this:
The data is encrypted. But, if we have a wifi password, we can decrypt it!
Go to Edit -> Preferences. Choose Protocol -> IEEE 802.11.
Press Edit for Decryption Keys. Press new. In popup enter:
Key type: wpa-pwd Key: password:wifiname
Now wireshark will decrypt the packets and we can see HTTP data as before:
Spy public wifi
I went to macdonalds with public wifi and for about an hour run wireshark (save only GET and POST HTTP requests). Saved fetched data into pcap file (File -> Save as). The question is, how to analyse such big amount of frames? Sometimes it is useful to export interesting data into CSV. Use tshark tool for that.
Save fields "frame number", "HTTP method", "full url" into results.csv:
tshark -r macdak_pushkin_get_post_only.pcap -T fields -e frame.number -e http.request.method -e http.request.full_uri > results.csv
Also i wrote small python script, that will count number of requests for each second-level domain. The first 20 results:
|Domain||Number of requests|
And yes, i've got couple of interesting sessions. For example, for site mamba.ru, session is transmitted by insecure HTTP protocol. So i copy the session, paste it in chrome using EditThisCookie plugin and voila, i am Sergey. I can read messages, look preferences and so on. Well, i just didn't want to harm Sergey, so i didn't make any POST requests.
How to protect
The only way to protect your site is to use TLS (HTTPS). To apply it well many things must be checked, but such discussion worth to make another post.
HTTPS traffic in wireshark:
All data is encrypted, we can't read it.
- Use HTTPS were possible, especially if you deal with important user data (if it includes bank/card data, you must use https)
- Being in public wifi network and accessing site by http, keep in mind, that it is very easy to spy for you. It applies to private network also, attacker just need to know the wifi password.
- Dan Callahan: Quick Wins for Better Website Security - PyCon 2014
- Hynek Schlawack: The Sorry State of SSL - PyCon 2014
- Benjamin Peterson - A Dive into TLS - PyCon 2015
- Ashwini Oruganti, Christopher Armstrong - Introduction to HTTPS: A Comedy of Errors - PyCon 2015
- Getting comfortable with web security: A hands-on session - PyCon 2015