2.04b: See changelog or extended commit message
- Option -V eliminated in favor of -W / -S. - Option -l added to limit the maximum requests per second (contributed by Sebastian Roschke) - Option -k added to limit the maximum duration of a scan (contributed by Sebastian Roschke) - Support for #ro, -W-; related documentation changes. - HTTPS -> HTTP form detection. - Added more diverse traversal and file disclosure tests (including file:// scheme tests) - Improved injection detection in <script> sections, where a ' or " is all we need to inject js code. - Added check to see if our injection strings end up server Set-Cookie, Set-Cookie2 and Content-Type reponse headers - URLs that give us a Javascript response are now tested with a "callback=" parameter to find JSONP issues. - Fixed "response varies" bug in 404 detection where a stable page would be marked unstable. - Bugfix to es / eg handling in dictionaries. - Added the "complete-fast.wl" wordlist which is an es / eg optimized version of "complete.wl" (resulting in 20-30% fewer requests).
This commit is contained in:
parent
987151620c
commit
a46315b1ec
32
ChangeLog
32
ChangeLog
|
@ -1,3 +1,35 @@
|
|||
Version 2.04b:
|
||||
--------------
|
||||
|
||||
- Option -V eliminated in favor of -W / -S.
|
||||
|
||||
- Option -l added to limit the maximum requests per second (contributed by Sebastian Roschke)
|
||||
|
||||
- Option -k added to limit the maximum duration of a scan (contributed by Sebastian Roschke)
|
||||
|
||||
- Support for #ro, -W-; related documentation changes.
|
||||
|
||||
- HTTPS -> HTTP form detection.
|
||||
|
||||
- Added more diverse traversal and file disclosure tests (including file:// scheme tests)
|
||||
|
||||
- Improved injection detection in <script> sections, where a ' or "
|
||||
is all we need to inject js code.
|
||||
|
||||
- Added check to see if our injection strings end up server Set-Cookie,
|
||||
Set-Cookie2 and Content-Type reponse headers
|
||||
|
||||
- URLs that give us a Javascript response are now tested with a
|
||||
"callback=" parameter to find JSONP issues.
|
||||
|
||||
- Fixed "response varies" bug in 404 detection where a stable page would be marked unstable.
|
||||
|
||||
- Bugfix to es / eg handling in dictionaries.
|
||||
|
||||
- Added the "complete-fast.wl" wordlist which is an es / eg optimized
|
||||
version of "complete.wl" (resulting in 20-30% fewer requests).
|
||||
|
||||
|
||||
Version 2.03b:
|
||||
--------------
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -20,7 +20,7 @@
|
|||
#
|
||||
|
||||
PROGNAME = skipfish
|
||||
VERSION = 2.03b
|
||||
VERSION = 2.04b
|
||||
|
||||
OBJFILES = http_client.c database.c crawler.c analysis.c report.c
|
||||
INCFILES = alloc-inl.h string-inl.h debug.h types.h http_client.h \
|
||||
|
|
72
README
72
README
|
@ -4,8 +4,14 @@ skipfish - web application security scanner
|
|||
|
||||
http://code.google.com/p/skipfish/
|
||||
|
||||
* Written and maintained by Michal Zalewski <lcamtuf@google.com>.
|
||||
* Copyright 2009, 2010, 2011 Google Inc, rights reserved.
|
||||
* Written and maintained by:
|
||||
|
||||
Michal Zalewski <lcamtuf@google.com>
|
||||
Niels Heinen <heinenn@google.com>
|
||||
Sebastian Roschke <s.roschke@googlemail.com>
|
||||
|
||||
* Copyright 2009 - 2012 Google Inc, rights reserved.
|
||||
|
||||
* Released under terms and conditions of the Apache License, version 2.0.
|
||||
|
||||
--------------------
|
||||
|
@ -111,7 +117,7 @@ A rough list of the security checks offered by the tool is outlined below.
|
|||
* Stored and reflected XSS vectors in document body (minimal JS XSS support).
|
||||
* Stored and reflected XSS vectors via HTTP redirects.
|
||||
* Stored and reflected XSS vectors via HTTP header splitting.
|
||||
* Directory traversal / RFI (including constrained vectors).
|
||||
* Directory traversal / LFI / RFI (including constrained vectors).
|
||||
* Assorted file POIs (server-side sources, configs, etc).
|
||||
* Attacker-supplied script and CSS inclusion vectors (stored and reflected).
|
||||
* External untrusted script and CSS inclusion vectors.
|
||||
|
@ -130,6 +136,7 @@ A rough list of the security checks offered by the tool is outlined below.
|
|||
* Attacker-supplied embedded content (stored and reflected).
|
||||
* External untrusted embedded content.
|
||||
* Mixed content on non-scriptable subresources (optional).
|
||||
* HTTPS -> HTTP submission of HTML forms (optional).
|
||||
* HTTP credentials in URLs.
|
||||
* Expired or not-yet-valid SSL certificates.
|
||||
* HTML forms with no XSRF protection.
|
||||
|
@ -228,20 +235,25 @@ behavior there.
|
|||
To compile it, simply unpack the archive and try make. Chances are, you will
|
||||
need to install libidn first.
|
||||
|
||||
Next, you need to copy the desired dictionary file from dictionaries/ to
|
||||
skipfish.wl. Please read dictionaries/README-FIRST carefully to make the
|
||||
right choice. This step has a profound impact on the quality of scan results
|
||||
later on.
|
||||
Next, you need to read the instructions provided in dictionaries/README-FIRST
|
||||
to select the right dictionary file and configure it correctly. This step has a
|
||||
profound impact on the quality of scan results later on, so don't skip it.
|
||||
|
||||
Once you have the dictionary selected, you can try:
|
||||
Once you have the dictionary selected, you can use -S to load that dictionary,
|
||||
and -W to specify an initially empty file for any newly learned site-specific
|
||||
keywords (which will come handy in future assessments):
|
||||
|
||||
$ ./skipfish -o output_dir http://www.example.com/some/starting/path.txt
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -o output_dir -S existing_dictionary.wl -W new_dict.wl \
|
||||
http://www.example.com/some/starting/path.txt
|
||||
|
||||
You can use -W- if you don't want to store auto-learned keywords anywhere.
|
||||
|
||||
Note that you can provide more than one starting URL if so desired; all of
|
||||
them will be crawled. It is also possible to read URLs from file, using
|
||||
the following syntax:
|
||||
|
||||
$ ./skipfish -o output_dir @../path/to/url_list.txt
|
||||
$ ./skipfish [...other options...] @../path/to/url_list.txt
|
||||
|
||||
The tool will display some helpful stats while the scan is in progress. You
|
||||
can also switch to a list of in-flight HTTP requests by pressing return.
|
||||
|
@ -373,6 +385,18 @@ restricted on HTTP/1.1 level, but no explicit HTTP/1.0 caching directive is
|
|||
given on specifying -E in the command-line causes skipfish to log all such
|
||||
cases carefully.
|
||||
|
||||
In some occasions, you want to limit the requests per second to limit
|
||||
the load on the targets server (or possibly bypass DoS protection). The
|
||||
-l flag can be used to set this limit and the value given is the maximum
|
||||
amount of requests per second you want skipfish to perform.
|
||||
|
||||
Scans typically should not take weeks. In many cases, you probably
|
||||
want to limit the scan duration so that it fits within a certain time
|
||||
window. This can be done with the -k flag, which allows the amount of
|
||||
hours, minutes and seconds to be specified in a H:M:S format. Use of
|
||||
this flag can affect the scan coverage if the scan timeout occurs before
|
||||
testing all pages.
|
||||
|
||||
Lastly, in some assessments that involve self-contained sites without
|
||||
extensive user content, the auditor may care about any external e-mails or
|
||||
HTTP links seen, even if they have no immediate security impact. Use the -U
|
||||
|
@ -380,10 +404,10 @@ option to have these logged.
|
|||
|
||||
Dictionary management is a special topic, and - as mentioned - is covered in
|
||||
more detail in dictionaries/README-FIRST. Please read that file before
|
||||
proceeding. Some of the relevant options include -W to specify a custom
|
||||
wordlist, -L to suppress auto-learning, -V to suppress dictionary updates, -G
|
||||
to limit the keyword guess jar size, -R to drop old dictionary entries, and
|
||||
-Y to inhibit expensive $keyword.$extension fuzzing.
|
||||
proceeding. Some of the relevant options include -S and -W (covered earlier),
|
||||
-L to suppress auto-learning, -G to limit the keyword guess jar size, -R to
|
||||
drop old dictionary entries, and -Y to inhibit expensive $keyword.$extension
|
||||
fuzzing.
|
||||
|
||||
Skipfish also features a form auto-completion mechanism in order to maximize
|
||||
scan coverage. The values should be non-malicious, as they are not meant to
|
||||
|
@ -423,21 +447,25 @@ Oh, and real-time scan statistics can be suppressed with -u.
|
|||
|
||||
A standard, authenticated scan of a well-designed and self-contained site
|
||||
(warns about all external links, e-mails, mixed content, and caching header
|
||||
issues):
|
||||
issues), including gentle brute-force:
|
||||
|
||||
$ ./skipfish -MEU -C "AuthCookie=value" -X /logout.aspx -o output_dir \
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -MEU -S dictionaries/minimal.wl -W new_dict.wl \
|
||||
-C "AuthCookie=value" -X /logout.aspx -o output_dir \
|
||||
http://www.example.com/
|
||||
|
||||
Five-connection crawl, but no brute-force; pretending to be MSIE and and
|
||||
trusting example.com content:
|
||||
|
||||
$ ./skipfish -m 5 -LV -W /dev/null -o output_dir -b ie -B example.com \
|
||||
$ ./skipfish -m 5 -L -W- -o output_dir -b ie -B example.com \
|
||||
http://www.example.com/
|
||||
|
||||
Brute force only (no HTML link extraction), limited to a single directory and
|
||||
timing out after 5 seconds:
|
||||
Heavy brute force only (no HTML link extraction), limited to a single
|
||||
directory and timing out after 5 seconds:
|
||||
|
||||
$ ./skipfish -P -I http://www.example.com/dir1/ -o output_dir -t 5 -I \
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -S dictionaries/complete.wl -W new_dict.wl \
|
||||
-P -I http://www.example.com/dir1/ -o output_dir -t 5 -I \
|
||||
http://www.example.com/dir1/
|
||||
|
||||
For a short list of all command-line options, try ./skipfish -h.
|
||||
|
@ -516,8 +544,6 @@ know:
|
|||
|
||||
* Scheduling and management web UI.
|
||||
|
||||
* QPS throttling and maximum scan time limit.
|
||||
|
||||
* A database for banner / version checks or other configurable rules?
|
||||
|
||||
-------------------------------------
|
||||
|
@ -560,4 +586,4 @@ Skipfish is made possible thanks to the contributions of, and valuable
|
|||
feedback from, Google's information security engineering team.
|
||||
|
||||
If you have any bug reports, questions, suggestions, or concerns regarding
|
||||
the application, the author can be reached at lcamtuf@google.com.
|
||||
the application, the primary author can be reached at lcamtuf@google.com.
|
||||
|
|
64
analysis.c
64
analysis.c
|
@ -111,7 +111,7 @@ void pivot_header_checks(struct http_request* req,
|
|||
/* Helper for scrape_response(). Tries to add a previously extracted link,
|
||||
also checks for cross-site and mixed content issues and similar woes.
|
||||
Subres is: 1 - redirect; 2 - IMG; 3 - IFRAME, EMBED, OBJECT, APPLET;
|
||||
4 - SCRIPT, LINK REL=STYLESHEET; 0 - everything else. */
|
||||
4 - SCRIPT, LINK REL=STYLESHEET; 5 - form; 0 - everything else. */
|
||||
|
||||
static void test_add_link(u8* str, struct http_request* ref,
|
||||
struct http_response* res, u8 subres, u8 sure) {
|
||||
|
@ -187,8 +187,11 @@ static void test_add_link(u8* str, struct http_request* ref,
|
|||
|
||||
if (ref->proto == PROTO_HTTPS && n->proto == PROTO_HTTP &&
|
||||
subres > 2 && warn_mixed)
|
||||
problem((subres == 4) ? PROB_MIXED_SUB : PROB_MIXED_OBJ,
|
||||
ref, res, str, ref->pivot, 0);
|
||||
switch (subres) {
|
||||
case 4: problem(PROB_MIXED_SUB, ref, res, str, ref->pivot, 0); break;
|
||||
case 5: problem(PROB_MIXED_FORM, ref, res, str, ref->pivot, 0); break;
|
||||
default: problem(PROB_MIXED_OBJ, ref, res, str, ref->pivot, 0);
|
||||
}
|
||||
|
||||
} else if (!ref->proto) {
|
||||
|
||||
|
@ -829,7 +832,7 @@ void scrape_response(struct http_request* req, struct http_response* res) {
|
|||
if (!parse_url(clean_url, n, base ? base : req) && url_allowed(n) &&
|
||||
R(100) < crawl_prob && !no_forms) {
|
||||
collect_form_data(n, req, res, tag_end + 1, (parse_form == 2));
|
||||
maybe_add_pivot(n, NULL, 2);
|
||||
maybe_add_pivot(n, NULL, 5);
|
||||
}
|
||||
|
||||
destroy_request(n);
|
||||
|
@ -1279,6 +1282,16 @@ static void check_js_xss(struct http_request* req, struct http_response* res,
|
|||
problem(PROB_URL_XSS, req, res,
|
||||
(u8*)"injected URL in JS/CSS code", req->pivot, 0);
|
||||
|
||||
u8* end_quote = text;
|
||||
while(*end_quote && end_quote++ && *end_quote != in_quot)
|
||||
if(*end_quote == '\\') end_quote++;
|
||||
|
||||
/* Injected string is 'skip'''"fish""" */
|
||||
if(end_quote && !case_prefix(end_quote + 1,"skip'''"))
|
||||
problem(PROB_URL_XSS, req, res, (u8*)"injected string in JS/CSS code (single quote not escaped)", req->pivot, 0);
|
||||
if(end_quote && !case_prefix(end_quote + 1,"fish\"\"\""))
|
||||
problem(PROB_URL_XSS, req, res, (u8*)"injected string in JS/CSS code (double quote not escaped)", req->pivot, 0);
|
||||
|
||||
} else if (in_quot && *text == in_quot) in_quot = 0;
|
||||
|
||||
else if (!in_quot && !case_prefix(text, "sfi") &&
|
||||
|
@ -1414,8 +1427,9 @@ next_elem:
|
|||
|
||||
void content_checks(struct http_request* req, struct http_response* res) {
|
||||
u8* tmp;
|
||||
u32 tag_id, scan_id;
|
||||
u32 off, tag_id, scan_id;
|
||||
u8 high_risk = 0;
|
||||
struct http_request* n;
|
||||
|
||||
DEBUG_CALLBACK(req, res);
|
||||
|
||||
|
@ -1461,6 +1475,24 @@ void content_checks(struct http_request* req, struct http_response* res) {
|
|||
h10c = 1;
|
||||
}
|
||||
|
||||
/* Check if injection strings ended up in one of our cookie name or
|
||||
values and complain */
|
||||
|
||||
u32 i = 0;
|
||||
|
||||
while(injection_headers[i]) {
|
||||
off = 0;
|
||||
|
||||
do {
|
||||
tmp = GET_HDR_OFF((u8*)injection_headers[i], &res->hdr,off++);
|
||||
if(tmp && strstr((char*)tmp, "skipfish://invalid/;"))
|
||||
problem(PROB_HEADER_INJECT,req, res,
|
||||
(u8*)injection_headers[i], req->pivot, 0);
|
||||
|
||||
} while(tmp);
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Check HTTP/1.1 intent next. Detect conflicting keywords. */
|
||||
|
||||
if (cc) {
|
||||
|
@ -1563,6 +1595,18 @@ void content_checks(struct http_request* req, struct http_response* res) {
|
|||
!inl_findstr(res->payload, (u8*)"function(", 1024))
|
||||
problem(PROB_JS_XSSI, req, res, NULL, req->pivot, 0);
|
||||
|
||||
|
||||
/* If the response resembles javascript and a callback parameter does
|
||||
not exist, we'll add this parameter in an attempt to catch JSONP
|
||||
issues */
|
||||
|
||||
if(is_javascript(res) && !GET_PAR((u8*)"callback", &req->par)) {
|
||||
n = req_copy(RPREQ(req), req->pivot, 1);
|
||||
SET_PAR((u8*)"callback",(u8*)"hello",&n->par);
|
||||
maybe_add_pivot(n, NULL, 2);
|
||||
destroy_request(n);
|
||||
}
|
||||
|
||||
tmp = res->payload;
|
||||
|
||||
do {
|
||||
|
@ -1884,6 +1928,7 @@ binary_checks:
|
|||
|
||||
static void detect_mime(struct http_request* req, struct http_response* res) {
|
||||
u8 sniffbuf[SNIFF_LEN];
|
||||
s32 fuzzy_match = -1;
|
||||
|
||||
if (res->sniff_mime_id) return;
|
||||
|
||||
|
@ -1900,7 +1945,7 @@ static void detect_mime(struct http_request* req, struct http_response* res) {
|
|||
while (mime_map[i][j]) {
|
||||
if (mime_map[i][j][0] == '?') {
|
||||
if (!strncasecmp((char*)mime_map[i][j] + 1, (char*)res->header_mime,
|
||||
strlen((char*)mime_map[i][j] + 1))) break;
|
||||
strlen((char*)mime_map[i][j] + 1))) fuzzy_match = i;
|
||||
} else {
|
||||
if (!strcasecmp((char*)mime_map[i][j], (char*)res->header_mime))
|
||||
break;
|
||||
|
@ -1912,8 +1957,11 @@ static void detect_mime(struct http_request* req, struct http_response* res) {
|
|||
|
||||
}
|
||||
|
||||
if (i != MIME_COUNT) res->decl_mime_id = i;
|
||||
|
||||
if (i != MIME_COUNT) {
|
||||
res->decl_mime_id = i;
|
||||
} else if (fuzzy_match != -1) {
|
||||
res->decl_mime_id = fuzzy_match;
|
||||
}
|
||||
}
|
||||
|
||||
/* Next, work out the actual MIME that should be set. Mostly
|
||||
|
|
12
analysis.h
12
analysis.h
|
@ -25,7 +25,6 @@
|
|||
#include "types.h"
|
||||
#include "http_client.h"
|
||||
#include "database.h"
|
||||
#include "crawler.h"
|
||||
|
||||
extern u8 no_parse, /* Disable HTML link detection */
|
||||
warn_mixed, /* Warn on mixed content */
|
||||
|
@ -202,6 +201,17 @@ static char* mime_map[MIME_COUNT][8] = {
|
|||
|
||||
};
|
||||
|
||||
/* A set of headers that we check to see if our injection string ended
|
||||
up in their value. This list should only contain headers where control
|
||||
over the value could potentially be exploited. */
|
||||
|
||||
static const char* injection_headers[] = {
|
||||
"Set-Cookie",
|
||||
"Set-Cookie2",
|
||||
"Content-Type",
|
||||
0,
|
||||
};
|
||||
|
||||
#endif /* _VIA_ANALYSIS_C */
|
||||
|
||||
#endif /* !_HAVE_ANALYSIS_H */
|
||||
|
|
|
@ -294,10 +294,12 @@ var issue_desc= {
|
|||
"30402": "Attacker-supplied URLs in embedded content (lower risk)",
|
||||
"30501": "External content embedded on a page (lower risk)",
|
||||
"30502": "Mixed content embedded on a page (lower risk)",
|
||||
"30503": "HTTPS form submitting to a HTTP URL",
|
||||
"30601": "HTML form with no apparent XSRF protection",
|
||||
"30602": "JSON response with no apparent XSSI protection",
|
||||
"30701": "Incorrect caching directives (lower risk)",
|
||||
"30801": "User-controlled response prefix (BOM / plugin attacks)",
|
||||
"30901": "HTTP header injection vector",
|
||||
|
||||
"40101": "XSS vector in document body",
|
||||
"40102": "XSS vector via arbitrary URLs",
|
||||
|
|
13
config.h
13
config.h
|
@ -36,13 +36,13 @@
|
|||
/* Default paths to runtime files: */
|
||||
|
||||
#define ASSETS_DIR "assets"
|
||||
#define DEF_WORDLIST "skipfish.wl"
|
||||
|
||||
/* Various default settings for HTTP client (cmdline override): */
|
||||
|
||||
#define MAX_CONNECTIONS 40 /* Simultaneous connection cap */
|
||||
#define MAX_CONN_HOST 10 /* Per-host connction cap */
|
||||
#define MAX_REQUESTS 1e8 /* Total request count cap */
|
||||
#define MAX_REQUESTS_SEC 0.0 /* Max requests per second */
|
||||
#define MAX_FAIL 100 /* Max consecutive failed requests */
|
||||
#define RW_TMOUT 10 /* Individual network R/W timeout */
|
||||
#define RESP_TMOUT 20 /* Total request time limit */
|
||||
|
@ -148,6 +148,17 @@
|
|||
#define XSRF_B64_NUM2 3 /* ...digit count override */
|
||||
#define XSRF_B64_SLASH 2 /* ...maximum slash count */
|
||||
|
||||
#ifdef _VIA_CRAWLER_C
|
||||
|
||||
/* The URL and string we use in the RFI test */
|
||||
|
||||
#ifdef RFI_SUPPORT
|
||||
#define RFI_HOST "http://www.google.com/humans.txt#foo="
|
||||
#define RFI_STRING "we can shake a stick"
|
||||
#endif
|
||||
|
||||
#endif /* _VIA_CRAWLER_C */
|
||||
|
||||
#ifdef _VIA_DATABASE_C
|
||||
|
||||
/* Domains we always trust (identical to -B options). These entries do not
|
||||
|
|
168
crawler.c
168
crawler.c
|
@ -598,7 +598,8 @@ static void secondary_ext_start(struct pivot_desc* pv, struct http_request* req,
|
|||
struct http_response* res, u8 is_param) {
|
||||
|
||||
u8 *base_name, *fpos, *lpos, *ex;
|
||||
s32 tpar = -1, i = 0, spar = -1;
|
||||
s32 tpar = -1, spar = -1;
|
||||
u32 i = 0;
|
||||
|
||||
DEBUG_HELPER(req->pivot);
|
||||
DEBUG_HELPER(pv);
|
||||
|
@ -720,12 +721,25 @@ static void inject_start(struct pivot_desc* pv) {
|
|||
|
||||
if (pv->type == PIVOT_DIR || pv->type == PIVOT_SERV) {
|
||||
struct http_request* n;
|
||||
|
||||
/* First a PUT request */
|
||||
n = req_copy(pv->req, pv, 1);
|
||||
if (n->method) ck_free(n->method);
|
||||
n->method = ck_strdup((u8*)"PUT");
|
||||
n->user_val = 0;
|
||||
n->callback = put_upload_check;
|
||||
replace_slash(n, (u8*)("PUT-" BOGUS_FILE));
|
||||
async_request(n);
|
||||
|
||||
/* Second a FOO for false positives */
|
||||
n = req_copy(pv->req, pv, 1);
|
||||
if (n->method) ck_free(n->method);
|
||||
n->method = ck_strdup((u8*)"FOO");
|
||||
n->user_val = 1;
|
||||
n->callback = put_upload_check;
|
||||
replace_slash(n, (u8*)("PUT-" BOGUS_FILE));
|
||||
async_request(n);
|
||||
|
||||
} else {
|
||||
inject_start2(pv);
|
||||
}
|
||||
|
@ -740,13 +754,22 @@ static u8 put_upload_check(struct http_request* req,
|
|||
|
||||
if (FETCH_FAIL(res)) {
|
||||
handle_error(req, res, (u8*)"during PUT checks", 0);
|
||||
} else {
|
||||
if (res->code >= 200 && res->code < 300 &&
|
||||
!same_page(&RPRES(req)->sig, &res->sig)) {
|
||||
problem(PROB_PUT_DIR, req, res, 0, req->pivot, 0);
|
||||
}
|
||||
goto schedule_next;
|
||||
}
|
||||
|
||||
req->pivot->misc_req[req->user_val] = req;
|
||||
req->pivot->misc_res[req->user_val] = res;
|
||||
if ((++req->pivot->misc_cnt) != 2) return 1;
|
||||
|
||||
/* If PUT and FOO of the page does not give the same result. And if
|
||||
additionally we get a 2xx code, than we'll mark the issue as detected */
|
||||
if(same_page(&MRES(0)->sig, &MRES(1)->sig) &&
|
||||
MRES(0)->code >= 200 && MRES(1)->code < 300)
|
||||
problem(PROB_PUT_DIR, MREQ(0), MRES(0), 0, req->pivot, 0);
|
||||
|
||||
schedule_next:
|
||||
|
||||
destroy_misc_data(req->pivot, req);
|
||||
inject_start2(req->pivot);
|
||||
return 0;
|
||||
|
||||
|
@ -773,8 +796,9 @@ static void inject_start2(struct pivot_desc* pv) {
|
|||
static u8 inject_behavior_check(struct http_request* req,
|
||||
struct http_response* res) {
|
||||
struct http_request* n;
|
||||
u32 orig_state = req->pivot->state;
|
||||
u8* tmp = NULL;
|
||||
u32 orig_state = req->pivot->state;
|
||||
u32 i;
|
||||
|
||||
/* pv->state may change after async_request() calls in
|
||||
insta-fail mode, so we should cache accordingly. */
|
||||
|
@ -867,29 +891,37 @@ static u8 inject_behavior_check(struct http_request* req,
|
|||
|
||||
if (orig_state != PSTATE_CHILD_INJECT) {
|
||||
|
||||
/* We combine the traversal and file disclosure attacks here since
|
||||
the checks are almost identical */
|
||||
|
||||
i = 0;
|
||||
while(disclosure_tests[i]) {
|
||||
n = req_copy(req->pivot->req, req->pivot, 1);
|
||||
|
||||
ck_free(TPAR(n));
|
||||
TPAR(n) = ck_strdup((u8*)disclosure_tests[i]);
|
||||
|
||||
n->callback = inject_dir_listing_check;
|
||||
n->user_val = 4 + i;
|
||||
async_request(n);
|
||||
i++;
|
||||
}
|
||||
|
||||
#ifdef RFI_SUPPORT
|
||||
/* Optionally try RFI */
|
||||
n = req_copy(req->pivot->req, req->pivot, 1);
|
||||
|
||||
ck_free(TPAR(n));
|
||||
TPAR(n) = ck_strdup((u8*)"../../../../../../../../etc/hosts");
|
||||
TPAR(n) = ck_strdup((u8*)RFI_HOST);
|
||||
|
||||
n->callback = inject_dir_listing_check;
|
||||
n->user_val = 4;
|
||||
async_request(n);
|
||||
|
||||
n = req_copy(req->pivot->req, req->pivot, 1);
|
||||
|
||||
ck_free(TPAR(n));
|
||||
TPAR(n) = ck_strdup((u8*)"..\\..\\..\\..\\..\\..\\..\\..\\boot.ini");
|
||||
|
||||
n->callback = inject_dir_listing_check;
|
||||
n->user_val = 5;
|
||||
n->user_val = 4 + i;
|
||||
async_request(n);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -912,7 +944,11 @@ static u8 inject_dir_listing_check(struct http_request* req,
|
|||
req->pivot->misc_res[req->user_val] = res;
|
||||
|
||||
if (req->pivot->i_skip_add) {
|
||||
if ((++req->pivot->misc_cnt) != 6) return 1;
|
||||
#ifdef RFI_SUPPORT
|
||||
if ((++req->pivot->misc_cnt) != 12) return 1;
|
||||
#else
|
||||
if ((++req->pivot->misc_cnt) != 11) return 1;
|
||||
#endif
|
||||
} else {
|
||||
if ((++req->pivot->misc_cnt) != 4) return 1;
|
||||
}
|
||||
|
@ -935,13 +971,22 @@ static u8 inject_dir_listing_check(struct http_request* req,
|
|||
misc[1] = ./known_val
|
||||
misc[2] = ...\known_val
|
||||
misc[3] = .\known_val
|
||||
misc[4] = ../../../../../../../../etc/hosts
|
||||
misc[5] = ..\..\..\..\..\..\..\..\boot.ini
|
||||
|
||||
Here, the test is simpler: if misc[1] != misc[0], or misc[3] !=
|
||||
misc[2], we probably have a bug. The same if misc[4] or misc[5]
|
||||
contain magic strings, but misc[0] doesn't.
|
||||
|
||||
Finally, we perform some directory traveral and file inclusion tests.
|
||||
|
||||
misc[4] = ../../../../../../../../etc/hosts
|
||||
misc[5] = ../../../../../../../../etc/passwd
|
||||
misc[6] = ..\..\..\..\..\..\..\..\boot.ini
|
||||
misc[7] = ../../../../../../../../WEB-INF/web.xml
|
||||
misc[8] = file:///etc/hosts
|
||||
misc[9] = file:///etc/passwd
|
||||
misc[10] = file:///boot.ini
|
||||
misc[11] = RFI (optional)
|
||||
|
||||
*/
|
||||
|
||||
if (orig_state == PSTATE_CHILD_INJECT) {
|
||||
|
@ -984,18 +1029,56 @@ static u8 inject_dir_listing_check(struct http_request* req,
|
|||
RESP_CHECKS(MREQ(2), MRES(2));
|
||||
}
|
||||
|
||||
if (inl_findstr(MRES(4)->payload, (u8*)"127.0.0.1", 512) &&
|
||||
!inl_findstr(MRES(0)->payload, (u8*)"127.0.0.1", 512)) {
|
||||
problem(PROB_DIR_TRAVERSAL, MREQ(4), MRES(4),
|
||||
(u8*)"response resembles /etc/hosts", req->pivot, 0);
|
||||
/* Check on the /etc/hosts file disclosure */
|
||||
if(!inl_findstr(MRES(0)->payload, (u8*)"127.0.0.1", 1024)) {
|
||||
if (inl_findstr(MRES(4)->payload, (u8*)"127.0.0.1", 1024)) {
|
||||
|
||||
problem(PROB_FI_LOCAL, MREQ(4), MRES(4),
|
||||
(u8*)"response resembles /etc/hosts (via traversal)", req->pivot, 0);
|
||||
} else if(inl_findstr(MRES(8)->payload, (u8*)"127.0.0.1", 1024)) {
|
||||
problem(PROB_FI_LOCAL, MREQ(8), MRES(8),
|
||||
(u8*)"response resembles /etc/hosts (via file://)", req->pivot, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (inl_findstr(MRES(5)->payload, (u8*)"[boot loader]", 512) &&
|
||||
!inl_findstr(MRES(0)->payload, (u8*)"[boot loader]", 512)) {
|
||||
problem(PROB_DIR_TRAVERSAL, MREQ(5), MRES(5),
|
||||
(u8*)"response resembles c:\\boot.ini", req->pivot, 0);
|
||||
/* Check on the /etc/passwd file disclosure */
|
||||
if(!inl_findstr(MRES(0)->payload, (u8*)"root:x:0:0:root", 1024)) {
|
||||
if(inl_findstr(MRES(5)->payload, (u8*)"root:x:0:0:root", 1024)) {
|
||||
problem(PROB_FI_LOCAL, MREQ(5), MRES(5),
|
||||
(u8*)"response resembles /etc/passwd (via traversal)", req->pivot, 0);
|
||||
} else if(inl_findstr(MRES(9)->payload, (u8*)"root:x:0:0:root", 1024)) {
|
||||
problem(PROB_FI_LOCAL, MREQ(9), MRES(9),
|
||||
(u8*)"response resembles /etc/passwd (via file://)", req->pivot, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Windows boot.ini disclosure */
|
||||
if(!inl_findstr(MRES(0)->payload, (u8*)"[boot loader]", 1024)) {
|
||||
if (inl_findstr(MRES(6)->payload, (u8*)"[boot loader]", 1024)) {
|
||||
problem(PROB_FI_LOCAL, MREQ(6), MRES(6),
|
||||
(u8*)"response resembles c:\\boot.ini (via traversal)", req->pivot, 0);
|
||||
} else if (inl_findstr(MRES(10)->payload, (u8*)"[boot loader]", 1024)) {
|
||||
problem(PROB_FI_LOCAL, MREQ(10), MRES(9),
|
||||
(u8*)"response resembles c:\\boot.ini (via file://)", req->pivot, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the web.xml disclosure */
|
||||
if(!inl_findstr(MRES(0)->payload, (u8*)"<servlet-mapping>", 1024)) {
|
||||
if (inl_findstr(MRES(7)->payload, (u8*)"<servlet-mapping>", 1024))
|
||||
problem(PROB_FI_LOCAL, MREQ(7), MRES(7),
|
||||
(u8*)"response resembles ./WEB-INF/web.xml (via traversal)", req->pivot, 0);
|
||||
}
|
||||
|
||||
|
||||
#ifdef RFI_SUPPORT
|
||||
if (!inl_findstr(MRES(0)->payload, (u8*)RFI_STRING, 1024) &&
|
||||
inl_findstr(MRES(11)->payload, (u8*)RFI_STRING, 1024)) {
|
||||
problem(PROB_FI_REMOTE, MREQ(11), MRES(11),
|
||||
(u8*)"remote file inclusion", req->pivot, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
schedule_next:
|
||||
|
@ -1232,7 +1315,7 @@ schedule_next:
|
|||
if (req->user_val) return 0;
|
||||
|
||||
n = req_copy(RPREQ(req), req->pivot, 1);
|
||||
SET_VECTOR(orig_state, n, (u8*)"SKIPFISH~STRING");
|
||||
SET_VECTOR(orig_state, n, (u8*)"+/skipfish-bom");
|
||||
n->callback = inject_prologue_check;
|
||||
async_request(n);
|
||||
|
||||
|
@ -1255,13 +1338,13 @@ static u8 inject_prologue_check(struct http_request* req,
|
|||
goto schedule_next;
|
||||
}
|
||||
|
||||
if (res->pay_len && !prefix(res->payload, (u8*)"SKIPFISH~STRING") &&
|
||||
if (res->pay_len && !prefix(res->payload, (u8*)"+/skipfish-bom") &&
|
||||
!GET_HDR((u8*)"Content-Disposition", &res->hdr))
|
||||
problem(PROB_PROLOGUE, req, res, NULL, req->pivot, 0);
|
||||
|
||||
schedule_next:
|
||||
|
||||
/* XSS checks - 3 requests */
|
||||
/* XSS checks - 4 requests */
|
||||
|
||||
n = req_copy(RPREQ(req), req->pivot, 1);
|
||||
SET_VECTOR(orig_state, n, "http://skipfish.invalid/;?");
|
||||
|
@ -1281,8 +1364,13 @@ schedule_next:
|
|||
n->user_val = 2;
|
||||
async_request(n);
|
||||
|
||||
return 0;
|
||||
n = req_copy(RPREQ(req), req->pivot, 1);
|
||||
SET_VECTOR(orig_state, n, "'skip'''\"fish\"\"\"");
|
||||
n->callback = inject_redir_check;
|
||||
n->user_val = 3;
|
||||
async_request(n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2755,9 +2843,14 @@ bad_404:
|
|||
} else {
|
||||
|
||||
if (req->pivot->type != PIVOT_SERV) {
|
||||
/* todo(niels) improve behavior by adding a new pivot */
|
||||
n = req_copy(RPREQ(req), req->pivot, 1);
|
||||
replace_slash(n, NULL);
|
||||
maybe_add_pivot(n, NULL, 2);
|
||||
req->pivot->type = PIVOT_PATHINFO;
|
||||
replace_slash(req->pivot->req, NULL);
|
||||
} else
|
||||
destroy_request(n);
|
||||
|
||||
} else
|
||||
problem(PROB_404_FAIL, RPREQ(req), RPRES(req),
|
||||
(u8*)"no distinctive 404 behavior detected", req->pivot, 0);
|
||||
}
|
||||
|
@ -3395,4 +3488,3 @@ schedule_next:
|
|||
|
||||
return keep;
|
||||
}
|
||||
|
||||
|
|
24
crawler.h
24
crawler.h
|
@ -26,6 +26,25 @@
|
|||
#include "http_client.h"
|
||||
#include "database.h"
|
||||
|
||||
#ifdef _VIA_CRAWLER_C
|
||||
|
||||
/* Strings for traversal and file disclosure tests. The order should
|
||||
not be changed */
|
||||
|
||||
static const char* disclosure_tests[] = {
|
||||
"../../../../../../../../etc/hosts",
|
||||
"../../../../../../../../etc/passwd",
|
||||
"..\\..\\..\\..\\..\\..\\..\\..\\boot.ini",
|
||||
"../../../../../../../../WEB-INF/web.xml",
|
||||
"file:///etc/hosts",
|
||||
"file:///etc/passwd",
|
||||
"file:///boot.ini",
|
||||
0
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
extern u32 crawl_prob; /* Crawl probability (1-100%) */
|
||||
extern u8 no_parse, /* Disable HTML link detection */
|
||||
warn_mixed, /* Warn on mixed content? */
|
||||
|
@ -64,11 +83,12 @@ void add_form_hint(u8* name, u8* value);
|
|||
|
||||
/* Macros to access various useful pivot points: */
|
||||
|
||||
#define MREQ(_x) (req->pivot->misc_req[_x])
|
||||
#define MRES(_x) (req->pivot->misc_res[_x])
|
||||
#define RPAR(_req) ((_req)->pivot->parent)
|
||||
#define RPREQ(_req) ((_req)->pivot->req)
|
||||
#define RPRES(_req) ((_req)->pivot->res)
|
||||
#define MREQ(_x) (req->pivot->misc_req[_x])
|
||||
#define MRES(_x) (req->pivot->misc_res[_x])
|
||||
|
||||
|
||||
/* Debugging instrumentation for callbacks and callback helpers: */
|
||||
|
||||
|
|
77
database.c
77
database.c
|
@ -82,14 +82,14 @@ struct ext_entry {
|
|||
u32 index;
|
||||
};
|
||||
|
||||
static struct ext_entry *extension, /* Extension list */
|
||||
*sp_extension;
|
||||
static struct ext_entry *wg_extension, /* Extension list */
|
||||
*ws_extension;
|
||||
|
||||
static u8 **guess; /* Keyword candidate list */
|
||||
|
||||
u32 guess_cnt, /* Number of keyword candidates */
|
||||
extension_cnt, /* Number of extensions */
|
||||
sp_extension_cnt, /* Number of specific extensions */
|
||||
ws_extension_cnt, /* Number of specific extensions */
|
||||
wg_extension_cnt, /* Number of extensions */
|
||||
keyword_total_cnt, /* Current keyword count */
|
||||
keyword_orig_cnt; /* At-boot keyword count */
|
||||
|
||||
|
@ -165,8 +165,9 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
|
|||
|
||||
if (PATH_SUBTYPE(req->par.t[i])) {
|
||||
|
||||
if (req->par.t[i] == PARAM_PATH && !req->par.n[i] && !req->par.v[i][0])
|
||||
ends_with_slash = 1;
|
||||
if (req->par.t[i] == PARAM_PATH && !req->par.n[i] &&
|
||||
req->par.v[i] && !req->par.v[i][0])
|
||||
ends_with_slash = 1;
|
||||
else
|
||||
ends_with_slash = 0;
|
||||
|
||||
|
@ -898,10 +899,10 @@ static void wordlist_confirm_single(u8* text, u8 is_ext, u8 class, u8 read_only,
|
|||
if (!keyword[kh][i].is_ext && is_ext) {
|
||||
keyword[kh][i].is_ext = 1;
|
||||
|
||||
extension = ck_realloc(extension, (extension_cnt + 1) *
|
||||
wg_extension = ck_realloc(wg_extension, (wg_extension_cnt + 1) *
|
||||
sizeof(struct ext_entry));
|
||||
extension[extension_cnt].bucket = kh;
|
||||
extension[extension_cnt++].index = i;
|
||||
wg_extension[wg_extension_cnt].bucket = kh;
|
||||
wg_extension[wg_extension_cnt++].index = i;
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -929,20 +930,18 @@ static void wordlist_confirm_single(u8* text, u8 is_ext, u8 class, u8 read_only,
|
|||
|
||||
if (is_ext) {
|
||||
|
||||
extension = ck_realloc(extension, (extension_cnt + 1) *
|
||||
wg_extension = ck_realloc(wg_extension, (wg_extension_cnt + 1) *
|
||||
sizeof(struct ext_entry));
|
||||
extension[extension_cnt].bucket = kh;
|
||||
extension[extension_cnt++].index = i;
|
||||
|
||||
if (class == KW_SPECIFIC) {
|
||||
|
||||
sp_extension = ck_realloc(sp_extension, (sp_extension_cnt + 1) *
|
||||
sizeof(struct ext_entry));
|
||||
sp_extension[sp_extension_cnt].bucket = kh;
|
||||
sp_extension[sp_extension_cnt++].index = i;
|
||||
wg_extension[wg_extension_cnt].bucket = kh;
|
||||
wg_extension[wg_extension_cnt++].index = i;
|
||||
|
||||
/* We only add generic extensions to the ws list. */
|
||||
if (class == KW_GENERIC) {
|
||||
ws_extension = ck_realloc(ws_extension, (ws_extension_cnt + 1) *
|
||||
sizeof(struct ext_entry));
|
||||
ws_extension[ws_extension_cnt].bucket = kh;
|
||||
ws_extension[ws_extension_cnt++].index = i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1065,12 +1064,12 @@ u8* wordlist_get_guess(u32 offset, u8* specific) {
|
|||
u8* wordlist_get_extension(u32 offset, u8 specific) {
|
||||
|
||||
if (!specific) {
|
||||
if (offset >= extension_cnt) return NULL;
|
||||
return keyword[extension[offset].bucket][extension[offset].index].word;
|
||||
if (offset >= wg_extension_cnt) return NULL;
|
||||
return keyword[wg_extension[offset].bucket][wg_extension[offset].index].word;
|
||||
}
|
||||
|
||||
if (offset >= sp_extension_cnt) return NULL;
|
||||
return keyword[sp_extension[offset].bucket][sp_extension[offset].index].word;
|
||||
if (offset >= ws_extension_cnt) return NULL;
|
||||
return keyword[ws_extension[offset].bucket][ws_extension[offset].index].word;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1089,12 +1088,16 @@ void load_keywords(u8* fname, u8 read_only, u32 purge_age) {
|
|||
in = fopen((char*)fname, "r");
|
||||
|
||||
if (!in) {
|
||||
PFATAL("Unable to open wordlist '%s'", fname);
|
||||
return;
|
||||
if (read_only)
|
||||
PFATAL("Unable to open read-only wordlist '%s'.", fname);
|
||||
else
|
||||
PFATAL("Unable to open read-write wordlist '%s' (see dictionaries/README-FIRST).", fname);
|
||||
}
|
||||
|
||||
sprintf(fmt, "%%2s %%u %%u %%u %%%u[^\x01-\x1f]", MAX_WORD);
|
||||
|
||||
wordlist_retry:
|
||||
|
||||
while ((fields = fscanf(in, fmt, type, &hits, &total_age, &last_age, kword))
|
||||
== 5) {
|
||||
|
||||
|
@ -1113,12 +1116,24 @@ void load_keywords(u8* fname, u8 read_only, u32 purge_age) {
|
|||
fgetc(in); /* sink \n */
|
||||
}
|
||||
|
||||
if (fields == 1 && !strcmp((char*)type, "#r")) {
|
||||
printf("Found %s (readonly:%d)\n", type, read_only);
|
||||
if (!read_only)
|
||||
FATAL("Attempt to load read-only wordlist '%s' via -W (use -S instead).\n", fname);
|
||||
|
||||
fgetc(in); /* sink \n */
|
||||
goto wordlist_retry;
|
||||
}
|
||||
|
||||
if (fields != -1 && fields != 5)
|
||||
FATAL("Wordlist '%s': syntax error in line %u.\n", fname, lines);
|
||||
|
||||
if (!lines)
|
||||
if (!lines && (read_only || !keyword_total_cnt))
|
||||
WARN("Wordlist '%s' contained no valid entries.", fname);
|
||||
|
||||
DEBUG("* Read %d lines from dictionary '%s' (read-only = %d).\n", lines,
|
||||
fname, read_only);
|
||||
|
||||
keyword_orig_cnt = keyword_total_cnt;
|
||||
|
||||
fclose(in);
|
||||
|
@ -1151,6 +1166,8 @@ void save_keywords(u8* fname) {
|
|||
#define O_NOFOLLOW 0
|
||||
#endif /* !O_NOFOLLOW */
|
||||
|
||||
/* Don't save keywords for /dev/null and other weird files. */
|
||||
|
||||
if (stat((char*)fname, &st) || !S_ISREG(st.st_mode)) return;
|
||||
|
||||
/* First, sort the list. */
|
||||
|
@ -1281,7 +1298,7 @@ void database_stats() {
|
|||
pivot_serv, pivot_dir, pivot_file, pivot_pinfo, pivot_unknown,
|
||||
pivot_param, pivot_value, issue_cnt[1], issue_cnt[2], issue_cnt[3],
|
||||
issue_cnt[4], issue_cnt[5], keyword_total_cnt, keyword_total_cnt -
|
||||
keyword_orig_cnt, extension_cnt, guess_cnt);
|
||||
keyword_orig_cnt, wg_extension_cnt, guess_cnt);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1486,8 +1503,8 @@ void destroy_database() {
|
|||
}
|
||||
|
||||
/* Extensions just referenced keyword[][] entries. */
|
||||
ck_free(extension);
|
||||
ck_free(sp_extension);
|
||||
ck_free(wg_extension);
|
||||
ck_free(ws_extension);
|
||||
|
||||
for (i=0;i<guess_cnt;i++) ck_free(guess[i]);
|
||||
ck_free(guess);
|
||||
|
|
|
@ -133,7 +133,7 @@ struct pivot_desc {
|
|||
|
||||
/* Injection attack logic scratchpad: */
|
||||
|
||||
#define MISC_ENTRIES 10
|
||||
#define MISC_ENTRIES 15
|
||||
|
||||
struct http_request* misc_req[MISC_ENTRIES]; /* Saved requests */
|
||||
struct http_response* misc_res[MISC_ENTRIES]; /* Saved responses */
|
||||
|
@ -258,6 +258,7 @@ u8 is_c_sens(struct pivot_desc* pv);
|
|||
|
||||
#define PROB_EXT_OBJ 30501 /* External obj standalone */
|
||||
#define PROB_MIXED_OBJ 30502 /* Mixed content standalone */
|
||||
#define PROB_MIXED_FORM 30503 /* HTTPS -> HTTP form */
|
||||
|
||||
#define PROB_VULN_FORM 30601 /* Form w/o anti-XSRF token */
|
||||
#define PROB_JS_XSSI 30602 /* Script with no XSSI prot */
|
||||
|
@ -266,6 +267,8 @@ u8 is_c_sens(struct pivot_desc* pv);
|
|||
|
||||
#define PROB_PROLOGUE 30801 /* User-supplied prologue */
|
||||
|
||||
#define PROB_HEADER_INJECT 30901 /* Injected string in header */
|
||||
|
||||
/* - Moderate severity issues (data compromise): */
|
||||
|
||||
#define PROB_BODY_XSS 40101 /* Document body XSS */
|
||||
|
@ -297,6 +300,8 @@ u8 is_c_sens(struct pivot_desc* pv);
|
|||
#define PROB_SQL_INJECT 50103 /* SQL injection */
|
||||
#define PROB_FMT_STRING 50104 /* Format string attack */
|
||||
#define PROB_INT_OVER 50105 /* Integer overflow attack */
|
||||
#define PROB_FI_LOCAL 50106 /* Local file inclusion */
|
||||
#define PROB_FI_REMOTE 50107 /* Local remote inclusion */
|
||||
|
||||
#define PROB_SQL_PARAM 50201 /* SQL-like parameter */
|
||||
|
||||
|
@ -353,7 +358,7 @@ extern u32 max_depth,
|
|||
max_guesses;
|
||||
|
||||
extern u32 guess_cnt,
|
||||
extension_cnt,
|
||||
wg_extension_cnt,
|
||||
keyword_total_cnt,
|
||||
keyword_orig_cnt;
|
||||
|
||||
|
|
|
@ -1,33 +1,50 @@
|
|||
This directory contains four alternative, hand-picked Skipfish dictionaries.
|
||||
|
||||
PLEASE READ THIS FILE CAREFULLY BEFORE PICKING ONE. This is *critical* to
|
||||
getting good results in your work.
|
||||
PLEASE READ THESE INSTRUCTIONS CAREFULLY BEFORE PICKING ONE. This is *critical*
|
||||
to getting decent results in your scans.
|
||||
|
||||
------------------------
|
||||
Key command-line options
|
||||
------------------------
|
||||
|
||||
The dictionary to be used by the tool can be specified with the -W option,
|
||||
and must conform to the format outlined at the end of this document. If you
|
||||
omit -W in the command line, 'skipfish.wl' is assumed. This file does not
|
||||
exist by default. That part is by design: THE SCANNER WILL MODIFY THE
|
||||
SUPPLIED FILE UNLESS SPECIFICALLY INSTRUCTED NOT TO.
|
||||
Skipfish automatically builds and maintain dictionaries based on the URLs
|
||||
and HTML content encountered while crawling the site. These dictionaries
|
||||
are extremely useful for subsequent scans of the same target, or for
|
||||
future assessments of other platforms belonging to the same customer.
|
||||
|
||||
That's because the scanner automatically learns new keywords and extensions
|
||||
based on any links discovered during the scan, and on random sampling of
|
||||
site contents. The information is consequently stored in the dictionary
|
||||
for future reuse, along with other bookkeeping information useful for
|
||||
determining which keywords perform well, and which ones don't.
|
||||
Exactly one read-write dictionary needs to be specified for every scan. To
|
||||
create a blank one and feed it to skipfish, you can use this syntax:
|
||||
|
||||
All this means that it is very important to maintain a separate dictionary
|
||||
for every separate set of unrelated target sites. Otherwise, undesirable
|
||||
interference will occur.
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -W new_dict.wl [...other options...]
|
||||
|
||||
In addition, it is almost always beneficial to seed the scanner with any
|
||||
number of supplemental, read-only dictionaries with common keywords useful
|
||||
for brute-force attacks against any website. Supplemental dictionaries can
|
||||
be loaded using the following syntax:
|
||||
|
||||
$ ./skipfish -S supplemental_dict1.wl -S supplemental_dict2.wl \
|
||||
-W new_dict.wl [...other options...]
|
||||
|
||||
Note that the -W option should be used with care. The target file will be
|
||||
modified at the end of the scan. The -S dictionaries, on the other hand, are
|
||||
purely read-only.
|
||||
|
||||
If you don't want to create a dictionary and store discovered keywords, you
|
||||
can use -W- (an alias for -W /dev/null).
|
||||
|
||||
You can and should share read-only dictionaries across unrelated scans, but
|
||||
a separate read-write dictionary should be used for scans of unrelated targets.
|
||||
Otherwise, undesirable interference may occur.
|
||||
|
||||
With this out of the way, let's quickly review the options that may be used
|
||||
to fine-tune various aspects of dictionary handling:
|
||||
|
||||
-L - do not automatically learn new keywords based on site content.
|
||||
|
||||
The scanner will still use keywords found in the specified
|
||||
dictionaries, if any; but will not go beyond that set.
|
||||
|
||||
This option should not be normally used in most scanning
|
||||
modes; if supplied, the scanner will not be able to discover
|
||||
and leverage technology-specific terms and file extensions
|
||||
|
@ -42,19 +59,12 @@ to fine-tune various aspects of dictionary handling:
|
|||
gradually replaced with new picks, and then discarded at the
|
||||
end of the scan. The default jar size is 256.
|
||||
|
||||
-V - prevent the scanner from updating the dictionary file.
|
||||
|
||||
Normally, the primary read-write dictionary specified with the
|
||||
-W option is updated at the end of the scan to add any newly
|
||||
discovered keywords, and to update keyword usage stats. Using
|
||||
this option eliminates this step.
|
||||
|
||||
-R num - purge all dictionary entries that had no non-404 hits for
|
||||
-R num - purge all entries in the read-write that had no non-404 hits for
|
||||
the last <num> scans.
|
||||
|
||||
This option prevents dictionary creep in repeated assessments,
|
||||
but needs to be used with care: it will permanently nuke a
|
||||
part of the dictionary!
|
||||
part of the dictionary.
|
||||
|
||||
-Y - inhibit full ${filename}.${extension} brute-force.
|
||||
|
||||
|
@ -77,7 +87,7 @@ associated request cost):
|
|||
scanner will not discover non-linked resources such as /admin,
|
||||
/index.php.old, etc:
|
||||
|
||||
./skipfish -W /dev/null -LV [...other options...]
|
||||
$ ./skipfish -W- -LV [...other options...]
|
||||
|
||||
This mode is very fast, but *NOT* recommended for general use because of
|
||||
limited coverage. Use only where absolutely necessary.
|
||||
|
@ -86,8 +96,9 @@ associated request cost):
|
|||
will not discover resources such as /admin, but will discover cases such as
|
||||
/index.php.old (once index.php itself is spotted during an orderly crawl):
|
||||
|
||||
cp dictionaries/extensions-only.wl dictionary.wl
|
||||
./skipfish -W dictionary.wl -Y [...other options...]
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -S dictionaries/extensions-only.wl -W new_dict.wl -Y \
|
||||
[...other options...]
|
||||
|
||||
This method is only slightly more request-intensive than #1, and therefore,
|
||||
is a marginally better alternative in cases where time is of essence. It's
|
||||
|
@ -98,8 +109,9 @@ associated request cost):
|
|||
try fuzzing the file name, or the extension, at any given time - but will
|
||||
not try every possible ${filename}.${extension} pair from the dictionary.
|
||||
|
||||
cp dictionaries/complete.wl dictionary.wl
|
||||
./skipfish -W dictionary.wl -Y [...other options...]
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -S dictionaries/complete.wl -W new_dict.wl -Y \
|
||||
[...other options...]
|
||||
|
||||
This method has a cost of about 2,000 requests per fuzzed location, and is
|
||||
recommended for rapid assessments, especially when working with slow
|
||||
|
@ -109,41 +121,27 @@ associated request cost):
|
|||
pair will be attempted. This mode is significantly slower, but offers
|
||||
superior coverage, and should be your starting point.
|
||||
|
||||
cp dictionaries/XXX.wl dictionary.wl
|
||||
./skipfish -W dictionary.wl [...other options...]
|
||||
$ touch new_dict.wl
|
||||
$ ./skipfish -S dictionaries/XXX.wl -W new_dict.wl [...other options...]
|
||||
|
||||
Replace XXX with:
|
||||
|
||||
minimal - recommended starter dictionary, mostly focusing on backup
|
||||
and source files, about 60,000 requests per fuzzed location.
|
||||
minimal - recommended starter dictionary, mostly focusing on
|
||||
backup and source files, about 60,000 requests
|
||||
per fuzzed location.
|
||||
|
||||
medium - more thorough dictionary, focusing on common frameworks,
|
||||
about 140,000 requests.
|
||||
medium - more thorough dictionary, focusing on common
|
||||
frameworks, about 140,000 requests.
|
||||
|
||||
complete - all-inclusive dictionary, over 210,000 requests.
|
||||
complete - all-inclusive dictionary, over 210,000 requests.
|
||||
|
||||
complete-fast - An optimized version of the 'complete' dictionary
|
||||
with 20-30% less requests.
|
||||
|
||||
Normal fuzzing mode is recommended when doing thorough assessments of
|
||||
reasonably responsive servers; but it may be prohibitively expensive
|
||||
when dealing with very large or very slow sites.
|
||||
|
||||
----------------------------------
|
||||
Using separate master dictionaries
|
||||
----------------------------------
|
||||
|
||||
A recently introduced feature allows you to load any number of read-only
|
||||
supplementary dictionaries in addition to the "main" read-write one (-W
|
||||
dictionary.wl).
|
||||
|
||||
This is a convenient way to isolate (and be able to continually update) your
|
||||
customized top-level wordlist, whilst still acquiring site-specific data in
|
||||
a separate file. The following syntax may be used to accomplish this:
|
||||
|
||||
./skipfish -W initially_empty_site_specific_dict.wl -W +supplementary_dict1.wl \
|
||||
-W +supplementary_dict2.wl [...other options...]
|
||||
|
||||
Only the main dictionary will be modified as a result of the scan, and only
|
||||
newly discovered site-specific keywords will be appended there.
|
||||
|
||||
----------------------------
|
||||
More about dictionary design
|
||||
----------------------------
|
||||
|
@ -204,7 +202,7 @@ files such as ${this_word}.php or ${this_word}.class. If not, tag the keyword
|
|||
as 'ws'.
|
||||
|
||||
Similarly, to decide between 'eg' and 'es', think about the possibility of
|
||||
encoutering cgi-bin.${this_ext} or formmail.${this_ext}. If it seems unlikely,
|
||||
encountering cgi-bin.${this_ext} or zencart.${this_ext}. If it seems unlikely,
|
||||
choose 'es'.
|
||||
|
||||
For your convenience, all legacy keywords and extensions, as well as any entries
|
||||
|
@ -222,6 +220,9 @@ Other notes about dictionaries:
|
|||
and all keywords should be NOT url-encoded (e.g., 'Program Files', not
|
||||
'Program%20Files'). No keyword should exceed 64 characters.
|
||||
|
||||
- Any valuable dictionary can be tagged with an optional '#ro' line at the
|
||||
beginning. This prevents it from being loaded in read-write mode.
|
||||
|
||||
- Tread carefully; poor wordlists are one of the reasons why some web security
|
||||
scanners perform worse than expected. You will almost always be better off
|
||||
narrowing down or selectively extending the supplied set (and possibly
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
|||
#ro
|
||||
e 1 1 1 7z
|
||||
e 1 1 1 as
|
||||
e 1 1 1 asmx
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#ro
|
||||
e 1 1 1 7z
|
||||
e 1 1 1 as
|
||||
e 1 1 1 asmx
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#ro
|
||||
e 1 1 1 asmx
|
||||
e 1 1 1 asp
|
||||
e 1 1 1 aspx
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#ro
|
||||
e 1 1 1 bak
|
||||
e 1 1 1 cfg
|
||||
e 1 1 1 class
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
es 1 2 2 php
|
||||
ws 1 2 2 cgi-bin
|
||||
eg 1 2 2 old
|
||||
wg 1 2 2 admin
|
||||
w? 1 0 0 localhost
|
|
@ -48,22 +48,26 @@
|
|||
|
||||
/* Assorted exported settings: */
|
||||
|
||||
u32 max_connections = MAX_CONNECTIONS,
|
||||
max_conn_host = MAX_CONN_HOST,
|
||||
max_requests = MAX_REQUESTS,
|
||||
max_fail = MAX_FAIL,
|
||||
idle_tmout = IDLE_TMOUT,
|
||||
resp_tmout = RESP_TMOUT,
|
||||
rw_tmout = RW_TMOUT,
|
||||
size_limit = SIZE_LIMIT;
|
||||
u32 max_connections = MAX_CONNECTIONS,
|
||||
max_conn_host = MAX_CONN_HOST,
|
||||
max_requests = MAX_REQUESTS,
|
||||
max_fail = MAX_FAIL,
|
||||
idle_tmout = IDLE_TMOUT,
|
||||
resp_tmout = RESP_TMOUT,
|
||||
rw_tmout = RW_TMOUT,
|
||||
size_limit = SIZE_LIMIT;
|
||||
|
||||
u8 browser_type = BROWSER_FAST;
|
||||
u8 auth_type = AUTH_NONE;
|
||||
u8 browser_type = BROWSER_FAST;
|
||||
u8 auth_type = AUTH_NONE;
|
||||
|
||||
float max_requests_sec = MAX_REQUESTS_SEC;
|
||||
|
||||
struct param_array global_http_par;
|
||||
|
||||
/* Counters: */
|
||||
|
||||
float req_sec;
|
||||
|
||||
u32 req_errors_net,
|
||||
req_errors_http,
|
||||
req_errors_cur,
|
||||
|
@ -81,18 +85,21 @@ u32 req_errors_net,
|
|||
u64 bytes_sent,
|
||||
bytes_recv,
|
||||
bytes_deflated,
|
||||
bytes_inflated;
|
||||
bytes_inflated,
|
||||
iterations_cnt = 0;
|
||||
|
||||
u8 *auth_user,
|
||||
*auth_pass;
|
||||
|
||||
|
||||
#ifdef PROXY_SUPPORT
|
||||
u8* use_proxy;
|
||||
u32 use_proxy_addr;
|
||||
u16 use_proxy_port;
|
||||
#endif /* PROXY_SUPPORT */
|
||||
|
||||
u8 ignore_cookies;
|
||||
u8 ignore_cookies,
|
||||
idle;
|
||||
|
||||
/* Internal globals for queue management: */
|
||||
|
||||
|
@ -127,7 +134,6 @@ u8* get_value(u8 type, u8* name, u32 offset,
|
|||
|
||||
}
|
||||
|
||||
|
||||
/* Inserts or overwrites parameter value in param_array. If offset
|
||||
== -1, will append parameter to list. Duplicates strings,
|
||||
name and val can be NULL. */
|
||||
|
@ -1483,6 +1489,7 @@ u8 parse_response(struct http_request* req, struct http_response* res,
|
|||
*val = 0;
|
||||
while (isspace(*(++val)));
|
||||
|
||||
SET_HDR(cur_line, val, &res->hdr);
|
||||
if (!strcasecmp((char*)cur_line, "Set-Cookie") ||
|
||||
!strcasecmp((char*)cur_line, "Set-Cookie2")) {
|
||||
|
||||
|
@ -1516,6 +1523,7 @@ u8 parse_response(struct http_request* req, struct http_response* res,
|
|||
strncmp((char*)cval, (char*)orig_val, 3))) {
|
||||
res->cookies_set = 1;
|
||||
problem(PROB_NEW_COOKIE, req, res, val, req->pivot, 0);
|
||||
|
||||
}
|
||||
|
||||
/* Set cookie globally, but ignore obvious attempts to delete
|
||||
|
@ -1525,8 +1533,7 @@ u8 parse_response(struct http_request* req, struct http_response* res,
|
|||
SET_CK(val, cval, &global_http_par);
|
||||
|
||||
}
|
||||
|
||||
} else SET_HDR(cur_line, val, &res->hdr);
|
||||
}
|
||||
|
||||
/* Content-Type is worth mining for MIME, charset data at this point. */
|
||||
|
||||
|
@ -2289,9 +2296,22 @@ SSL_read_more:
|
|||
struct queue_entry *q = queue;
|
||||
|
||||
while (q) {
|
||||
struct queue_entry* next = q->next;
|
||||
u32 to_host = 0;
|
||||
|
||||
// enforce the max requests per seconds requirement
|
||||
if (max_requests_sec && req_sec > max_requests_sec) {
|
||||
u32 diff = req_sec - max_requests_sec;
|
||||
|
||||
DEBUG("req_sec=%f max=%f diff=%u\n", req_sec, max_requests_sec, diff);
|
||||
if ((iterations_cnt++)%(diff + 1) != 0) {
|
||||
idle = 1;
|
||||
return queue_cur;
|
||||
}
|
||||
}
|
||||
idle = 0;
|
||||
|
||||
struct queue_entry* next = q->next;
|
||||
|
||||
if (!q->c) {
|
||||
|
||||
struct conn_entry* c = conn;
|
||||
|
@ -2457,7 +2477,6 @@ struct http_response* res_copy(struct http_response* res) {
|
|||
|
||||
}
|
||||
|
||||
|
||||
/* Dumps HTTP request data, for diagnostic purposes: */
|
||||
|
||||
void dump_http_request(struct http_request* r) {
|
||||
|
|
|
@ -266,10 +266,13 @@ void set_value(u8 type, u8* name, u8* val, s32 offset, struct param_array* par);
|
|||
|
||||
/* Simplified macros for value table access: */
|
||||
|
||||
#define GET_HDR(_name, _p) get_value(PARAM_HEADER, _name, 0, _p)
|
||||
#define SET_HDR(_name, _val, _p) set_value(PARAM_HEADER, _name, _val, -1, _p)
|
||||
#define GET_CK(_name, _p) get_value(PARAM_COOKIE, _name, 0, _p)
|
||||
#define SET_CK(_name, _val, _p) set_value(PARAM_COOKIE, _name, _val, 0, _p)
|
||||
#define GET_CK(_name, _p) get_value(PARAM_COOKIE, _name, 0, _p)
|
||||
#define SET_CK(_name, _val, _p) set_value(PARAM_COOKIE, _name, _val, 0, _p)
|
||||
#define GET_PAR(_name, _p) get_value(PARAM_QUERY, _name, 0, _p)
|
||||
#define SET_PAR(_name, _val, _p) set_value(PARAM_QUERY, _name, _val, -1, _p)
|
||||
#define GET_HDR(_name, _p) get_value(PARAM_HEADER, _name, 0, _p)
|
||||
#define SET_HDR(_name, _val, _p) set_value(PARAM_HEADER, _name, _val, -1, _p)
|
||||
#define GET_HDR_OFF(_name, _p, _o) get_value(PARAM_HEADER, _name, _o, _p)
|
||||
|
||||
void tokenize_path(u8* str, struct http_request* req, u8 add_slash);
|
||||
|
||||
|
@ -383,12 +386,17 @@ extern u32 max_connections,
|
|||
conn_failed,
|
||||
queue_cur;
|
||||
|
||||
extern float req_sec,
|
||||
max_requests_sec;
|
||||
|
||||
extern u64 bytes_sent,
|
||||
bytes_recv,
|
||||
bytes_deflated,
|
||||
bytes_inflated;
|
||||
bytes_inflated,
|
||||
iterations_cnt;
|
||||
|
||||
extern u8 ignore_cookies;
|
||||
extern u8 ignore_cookies,
|
||||
idle;
|
||||
|
||||
/* Flags for browser type: */
|
||||
|
||||
|
|
4
report.c
4
report.c
|
@ -421,7 +421,7 @@ static void save_req_res(struct http_request* req, struct http_response* res, u8
|
|||
ck_free(rd);
|
||||
}
|
||||
|
||||
if (res && res->state == STATE_OK) {
|
||||
if (res && req && res->state == STATE_OK) {
|
||||
u32 i;
|
||||
f = fopen("response.dat", "w");
|
||||
if (!f) PFATAL("Cannot create 'response.dat'");
|
||||
|
@ -430,8 +430,6 @@ static void save_req_res(struct http_request* req, struct http_response* res, u8
|
|||
for (i=0;i<res->hdr.c;i++)
|
||||
if (res->hdr.t[i] == PARAM_HEADER)
|
||||
fprintf(f, "%s: %s\n", res->hdr.n[i], res->hdr.v[i]);
|
||||
else
|
||||
fprintf(f, "Set-Cookie: %s=%s\n", res->hdr.n[i], res->hdr.v[i]);
|
||||
|
||||
fprintf(f, "\n");
|
||||
fwrite(res->payload, res->pay_len, 1, f);
|
||||
|
|
23
skipfish.1
23
skipfish.1
|
@ -4,7 +4,7 @@
|
|||
skipfish \- active web application security reconnaissance tool
|
||||
.SH SYNOPSIS
|
||||
.B skipfish
|
||||
.RI [ options ] " -o output-directory start-url [start-url2 ...]"
|
||||
.RI [ options ] " -W wordlist -o output-directory start-url [start-url2 ...]"
|
||||
.br
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
|
@ -100,15 +100,15 @@ be quiet, do not display realtime scan statistics
|
|||
|
||||
.SS Dictionary management options:
|
||||
.TP
|
||||
.B \-S wordlist
|
||||
load a specified read-only wordlist for brute-force tests
|
||||
.TP
|
||||
.B \-W wordlist
|
||||
load an alternative wordlist (skipfish.wl)
|
||||
load a specified read-write wordlist for any site-specific learned words. This option is required but the specified file can be empty, to store the newly learned words and alternatively, you can use -W- to discard new words.
|
||||
.TP
|
||||
.B \-L
|
||||
do not auto-learn new keywords for the site
|
||||
.TP
|
||||
.B \-V
|
||||
do not update wordlist based on scan results
|
||||
.TP
|
||||
.B \-Y
|
||||
do not fuzz extensions during most directory brute-force steps
|
||||
.TP
|
||||
|
@ -123,6 +123,9 @@ maximum number of keyword guesses to keep in the jar (default: 256)
|
|||
|
||||
.SS Performance settings:
|
||||
.TP
|
||||
.B \-l max_req
|
||||
max requests per second (0 = unlimited)
|
||||
.TP
|
||||
.B \-g max_conn
|
||||
maximum simultaneous TCP connections, global (default: 50)
|
||||
.TP
|
||||
|
@ -147,11 +150,15 @@ response size limit (default: 200000 B)
|
|||
.B \-e
|
||||
do not keep binary responses for reporting
|
||||
|
||||
.SS Performance settings:
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Show summary of options.
|
||||
.B \-k duration
|
||||
stop scanning after the given duration (format: h:m:s)
|
||||
|
||||
.SH AUTHOR
|
||||
skipfish was written by Michal Zalewski <lcamtuf@google.com>.
|
||||
skipfish was written by Michal Zalewski <lcamtuf@google.com>,
|
||||
with contributions from Niels Heinen <heinenn@google.com>,
|
||||
Sebastian Roschke <s.roschke@googlemail.com>, and other parties.
|
||||
.PP
|
||||
This manual page was written by Thorsten Schifferdecker <tsd@debian.systs.org>,
|
||||
for the Debian project (and may be used by others).
|
||||
|
|
101
skipfish.c
101
skipfish.c
|
@ -68,7 +68,7 @@ static void resize_handler(int sig) {
|
|||
/* Usage info. */
|
||||
|
||||
static void usage(char* argv0) {
|
||||
SAY("Usage: %s [ options ... ] -o output_dir start_url [ start_url2 ... ]\n\n"
|
||||
SAY("Usage: %s [ options ... ] -W wordlist -o output_dir start_url [ start_url2 ... ]\n\n"
|
||||
|
||||
"Authentication and access options:\n\n"
|
||||
|
||||
|
@ -110,9 +110,9 @@ static void usage(char* argv0) {
|
|||
|
||||
"Dictionary management options:\n\n"
|
||||
|
||||
" -W wordlist - load an alternative wordlist (%s)\n"
|
||||
" -W wordlist - use a specified read-write wordlist (required)\n"
|
||||
" -S wordlist - load a supplemental read-only wordlist\n"
|
||||
" -L - do not auto-learn new keywords for the site\n"
|
||||
" -V - do not update wordlist based on scan results\n"
|
||||
" -Y - do not fuzz extensions in directory brute-force\n"
|
||||
" -R age - purge words hit more than 'age' scans ago\n"
|
||||
" -T name=val - add new form auto-fill rule\n"
|
||||
|
@ -120,6 +120,7 @@ static void usage(char* argv0) {
|
|||
|
||||
"Performance settings:\n\n"
|
||||
|
||||
" -l max_req - max requests per second (%f)\n"
|
||||
" -g max_conn - max simultaneous TCP connections, global (%u)\n"
|
||||
" -m host_conn - max simultaneous connections, per target IP (%u)\n"
|
||||
" -f max_fail - max number of consecutive HTTP errors (%u)\n"
|
||||
|
@ -129,10 +130,14 @@ static void usage(char* argv0) {
|
|||
" -s s_limit - response size limit (%u B)\n"
|
||||
" -e - do not keep binary responses for reporting\n\n"
|
||||
|
||||
"Safety settings:\n\n"
|
||||
|
||||
" -k duration - stop scanning after the given duration h:m:s\n\n"
|
||||
|
||||
"Send comments and complaints to <lcamtuf@google.com>.\n", argv0,
|
||||
max_depth, max_children, max_descendants, max_requests, DEF_WORDLIST,
|
||||
MAX_GUESSES, max_connections, max_conn_host, max_fail, resp_tmout,
|
||||
rw_tmout, idle_tmout, size_limit);
|
||||
max_depth, max_children, max_descendants, max_requests,
|
||||
MAX_GUESSES, max_requests_sec, max_connections, max_conn_host,
|
||||
max_fail, resp_tmout, rw_tmout, idle_tmout, size_limit);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
@ -164,14 +169,14 @@ void splash_screen(void) {
|
|||
|
||||
"More info: " cYEL "http://code.google.com/p/skipfish/wiki/KnownIssues\n\n" cBRI);
|
||||
|
||||
if (!no_fuzz_ext && (keyword_orig_cnt * extension_cnt) > 1000) {
|
||||
if (!no_fuzz_ext && (keyword_orig_cnt * wg_extension_cnt) > 1000) {
|
||||
|
||||
SAY(cLRD
|
||||
|
||||
"NOTE: The scanner is currently configured for directory brute-force attacks,\n"
|
||||
"and will make about " cBRI "%u" cLRD " requests per every fuzzable location. If this is\n"
|
||||
"not what you wanted, stop now and consult the documentation.\n\n",
|
||||
keyword_orig_cnt * extension_cnt);
|
||||
keyword_orig_cnt * wg_extension_cnt);
|
||||
|
||||
}
|
||||
|
||||
|
@ -237,9 +242,10 @@ static void read_urls(u8* fn) {
|
|||
int main(int argc, char** argv) {
|
||||
s32 opt;
|
||||
u32 loop_cnt = 0, purge_age = 0, seed;
|
||||
u8 dont_save_words = 0, show_once = 0, be_quiet = 0, display_mode = 0,
|
||||
has_fake = 0;
|
||||
u8 show_once = 0, be_quiet = 0, display_mode = 0, has_fake = 0;
|
||||
u8 *wordlist = NULL, *output_dir = NULL;
|
||||
u8* gtimeout_str = NULL;
|
||||
u32 gtimeout = 0;
|
||||
|
||||
struct termios term;
|
||||
struct timeval tv;
|
||||
|
@ -258,7 +264,8 @@ int main(int argc, char** argv) {
|
|||
SAY("skipfish version " VERSION " by <lcamtuf@google.com>\n");
|
||||
|
||||
while ((opt = getopt(argc, argv,
|
||||
"+A:F:C:H:b:Nd:c:x:r:p:I:X:D:POYQMZUEK:W:LVT:J:G:R:B:q:g:m:f:t:w:i:s:o:hue")) > 0)
|
||||
"+A:B:C:D:EF:G:H:I:J:K:LMNOPQR:S:T:UW:X:YZ"
|
||||
"b:c:d:ef:g:hi:k:l:m:o:p:q:r:s:t:uw:x:")) > 0)
|
||||
|
||||
switch (opt) {
|
||||
|
||||
|
@ -371,10 +378,6 @@ int main(int argc, char** argv) {
|
|||
no_parse = 1;
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
dont_save_words = 1;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
warn_mixed = 1;
|
||||
break;
|
||||
|
@ -421,12 +424,16 @@ int main(int argc, char** argv) {
|
|||
break;
|
||||
|
||||
case 'W':
|
||||
if (optarg[0] == '+') load_keywords((u8*)optarg + 1, 1, 0);
|
||||
else {
|
||||
if (wordlist)
|
||||
FATAL("Only one -W parameter permitted (unless '+' used).");
|
||||
wordlist = (u8*)optarg;
|
||||
}
|
||||
if (wordlist)
|
||||
FATAL("Only one -W parameter permitted (use -S to load supplemental dictionaries).");
|
||||
|
||||
if (!strcmp(optarg, "-")) wordlist = (u8*)"/dev/null";
|
||||
else wordlist = (u8*)optarg;
|
||||
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
load_keywords((u8*)optarg, 1, 0);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
|
@ -456,6 +463,11 @@ int main(int argc, char** argv) {
|
|||
if (!max_requests) FATAL("Invalid value '%s'.", optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
max_requests_sec = atof(optarg);
|
||||
if (!max_requests_sec) FATAL("Invalid value '%s'.", optarg);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
max_fail = atoi(optarg);
|
||||
if (!max_fail) FATAL("Invalid value '%s'.", optarg);
|
||||
|
@ -500,6 +512,11 @@ int main(int argc, char** argv) {
|
|||
delete_bin = 1;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
if (gtimeout_str) FATAL("Multiple -k options not allowed.");
|
||||
gtimeout_str = (u8*)optarg;
|
||||
break;
|
||||
|
||||
case 'Z':
|
||||
no_500_dir = 1;
|
||||
break;
|
||||
|
@ -531,7 +548,27 @@ int main(int argc, char** argv) {
|
|||
if (max_connections < max_conn_host)
|
||||
max_connections = max_conn_host;
|
||||
|
||||
if (!wordlist) wordlist = (u8*)DEF_WORDLIST;
|
||||
/* Parse the timeout string - format h:m:s */
|
||||
if (gtimeout_str) {
|
||||
int i = 0;
|
||||
int m[3] = { 1, 60, 3600 };
|
||||
|
||||
u8* tok = (u8*)strtok((char*)gtimeout_str, ":");
|
||||
|
||||
while(tok && i <= 2) {
|
||||
gtimeout += atoi((char*)tok) * m[i];
|
||||
tok = (u8*)strtok(NULL, ":");
|
||||
i++;
|
||||
}
|
||||
|
||||
if(!gtimeout)
|
||||
FATAL("Wrong timeout format, please use h:m:s (hours, minutes, seconds)");
|
||||
DEBUG("* Scan timeout is set to %d seconds\n", gtimeout);
|
||||
}
|
||||
|
||||
|
||||
if (!wordlist)
|
||||
FATAL("Wordlist not specified (try -h for help; see dictionaries/README-FIRST).");
|
||||
|
||||
load_keywords(wordlist, 0, purge_age);
|
||||
|
||||
|
@ -587,7 +624,23 @@ int main(int argc, char** argv) {
|
|||
|
||||
u8 keybuf[8];
|
||||
|
||||
if (be_quiet || ((loop_cnt++ % 100) && !show_once)) continue;
|
||||
u64 end_time;
|
||||
u64 run_time;
|
||||
struct timeval tv_tmp;
|
||||
|
||||
gettimeofday(&tv_tmp, NULL);
|
||||
end_time = tv_tmp.tv_sec * 1000LL + tv_tmp.tv_usec / 1000;
|
||||
|
||||
run_time = end_time - st_time;
|
||||
if (gtimeout > 0 && run_time && run_time/1000 > gtimeout) {
|
||||
DEBUG("* Stopping scan due to timeout\n");
|
||||
stop_soon = 1;
|
||||
}
|
||||
|
||||
req_sec = (req_count - queue_cur / 1.15) * 1000 / (run_time + 1);
|
||||
|
||||
if (be_quiet || ((loop_cnt++ % 100) && !show_once && idle == 0))
|
||||
continue;
|
||||
|
||||
if (clear_screen) {
|
||||
SAY("\x1b[H\x1b[2J");
|
||||
|
@ -629,7 +682,7 @@ int main(int argc, char** argv) {
|
|||
tcsetattr(0, TCSANOW, &term);
|
||||
fcntl(0, F_SETFL, O_SYNC);
|
||||
|
||||
if (!dont_save_words) save_keywords((u8*)wordlist);
|
||||
save_keywords((u8*)wordlist);
|
||||
|
||||
write_report(output_dir, en_time - st_time, seed);
|
||||
|
||||
|
|
Loading…
Reference in New Issue