2.10b: Many updates, see changelog

This commit is contained in:
Steve Pinkham 2012-12-21 23:32:24 -05:00
parent e48969d827
commit 093800c9de
30 changed files with 1782 additions and 543 deletions

View File

@ -1,3 +1,44 @@
Version 2.10b:
- Updated HTML tags and attributes that are checked for URL XSS
injections to also include a few HTML5 specific ones
- Updated test and description for semi-colon injection in HTML meta
refresh tags (this is IE6 specific)
- Relaxed HTML parsing a bit to allow spaces between HTML tag attributes
and their values (e.g. "foo =bar").
- Major update of LFI tests by adding more dynamic tests (double
encoding, dynamic amount of ../'s for web.xml). The total amount of
tests for this vulnerability is now 40 per injection point.
- The RFI test is now a separate test and no longer requires special
compile options. The default RFI URL and it's payload check are
still defined in src/config.h.
- Using the --flush-to-disk flag will cause requests and responses
to be flushed to disk which reduces the memory footprint. (especially
noticable in large scans)
- Fixed a bug where in some conditions (e.g. a page looks similar to
another) links were not scraped from responses which lead to links
to be missed (thanks to Anurag Chaurasia for reporting)
- Added configuration file support with the --config flag. In
config/example.conf you can find flags and examples.
- Several signature keyword enhancements have been made. Most
significant are the "header" keyword, which allows header matching
and the "depend" keyword which allows signature chaining.
- Fixed basic authentication which was broken per 2.08b. Cheers to
Michael Stevens for reporting.
- Fixed -k scheduling where 1:0:0 would count as a second in stead of
an hour (also visa versa). Cheers to Claudio Criscione for reporting.
- Small fix to compile time warnings
Version 2.09b:
- Fixed a crash that could be triggered during 404 fingerprint failures

View File

@ -2,7 +2,8 @@
# skipfish - Makefile
# -------------------
#
# Author: Michal Zalewski <lcamtuf@google.com>
# Author: Michal Zalewski <lcamtuf@google.com>,
# Niels Heinen <heinenn@google.com>
#
# Copyright 2009, 2010, 2011 by Google Inc. All Rights Reserved.
#
@ -20,21 +21,22 @@
#
PROGNAME = skipfish
VERSION = 2.09b
VERSION = 2.10b
SRCDIR = src
SFILES = http_client.c database.c crawler.c analysis.c report.c \
checks.c signatures.c auth.c
checks.c signatures.c auth.c options.c
IFILES = alloc-inl.h string-inl.h debug.h types.h http_client.h \
database.h crawler.h analysis.h config.h report.h \
checks.h signatures.h auth.h
checks.h signatures.h auth.h options.h
OBJFILES = $(patsubst %,$(SRCDIR)/%,$(SFILES))
INCFILES = $(patsubst %,$(SRCDIR)/%,$(IFILES))
CFLAGS_GEN = -Wall -funsigned-char -g -ggdb -I/usr/local/include/ \
-I/opt/local/include/ $(CFLAGS) -DVERSION=\"$(VERSION)\"
CFLAGS_DBG = -DLOG_STDERR=1 -DDEBUG_ALLOCATOR=1 $(CFLAGS_GEN)
CFLAGS_DBG = -DLOG_STDERR=1 -DDEBUG_ALLOCATOR=1 \
$(CFLAGS_GEN)
CFLAGS_OPT = -O3 -Wno-format $(CFLAGS_GEN)
LDFLAGS += -L/usr/local/lib/ -L/opt/local/lib
@ -55,6 +57,12 @@ $(PROGNAME): $(SRCDIR)/$(PROGNAME).c $(OBJFILES) $(INCFILES)
debug: $(SRCDIR)/$(PROGNAME).c $(OBJFILES) $(INCFILES)
$(CC) $(LDFLAGS) $(SRCDIR)/$(PROGNAME).c -o $(PROGNAME) \
$(CFLAGS_DBG) $(OBJFILES) $(LIBS)
@echo
@echo "The debug build prints runtime information to stderr. You"
@echo "probably want to redirect this output to a file. like:"
@echo
@echo " $ ./skipfish [.option.] 2> debug.log"
@echo
clean:
rm -f $(PROGNAME) *.exe *.o *~ a.out core core.[1-9][0-9]* *.stackdump \
@ -66,6 +74,7 @@ same_test: $(SRCDIR)/same_test.c $(OBJFILES) $(INCFILES)
$(LIBS)
publish: clean
cd ..; rm -rf skipfish-$(VERSION); cp -pr skipfish skipfish-$(VERSION); \
tar cfvz ~/www/skipfish.tgz skipfish-$(VERSION)
cd ..; rm -rf skipfish-$(VERSION); \
cp -pr skipfish-release skipfish-$(VERSION); \
tar cfvz ~/www/skipfish.tgz skipfish-$(VERSION); \
chmod 644 ~/www/skipfish.tgz

10
README
View File

@ -238,7 +238,7 @@ 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 read the instructions provided in dictionaries/README-FIRST
Next, you need to read the instructions provided in doc/dictionaries.txt
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.
@ -278,7 +278,7 @@ new or changed nodes; and blue background to all new or changed issues
found.
Some sites may require authentication for which our support is described
in docs/authentication.txt. In most cases, you'll be wanting to use the
in doc/authentication.txt. In most cases, you'll be wanting to use the
form authentication method which is capable of detecting broken sessions
in order to re-authenticate.
@ -398,7 +398,7 @@ HTTP links seen, even if they have no immediate security impact. Use the -U
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
more detail in doc/dictionaries.txt. Please read that file before
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
@ -531,12 +531,8 @@ know:
* Scan resume option, better runtime info.
* Option to limit document sampling or save samples directly to disk.
* Standalone installation (make install) support.
* Config file support.
* Scheduling and management web UI.
-------------------------------------

View File

@ -261,7 +261,7 @@ var issue_desc= {
"10402": "HTTP authentication required",
"10403": "Server error triggered",
"10404": "Directory listing enabled",
"10405": "Discovered files / directories",
"10405": "Hidden files / directories",
"10501": "All external links",
"10502": "External URL redirector",
@ -304,6 +304,7 @@ var issue_desc= {
"30503": "HTTPS form submitting to a HTTP URL",
"30601": "HTML form with no apparent XSRF protection",
"30602": "JSON response with no apparent XSSI protection",
"30603": "Auth form leaks credentials via HTTP GET",
"30701": "Incorrect caching directives (lower risk)",
"30801": "User-controlled response prefix (BOM / plugin attacks)",
"30901": "HTTP header injection vector",
@ -333,6 +334,7 @@ var issue_desc= {
"50104": "Format string vector",
"50105": "Integer overflow vector",
"50106": "File inclusion",
"50107": "Remote file inclusion",
"50201": "SQL query or similar syntax in parameters",
"50301": "PUT request accepted",
"50909": "Signature match detected (high risk)"

188
config/example.conf Normal file
View File

@ -0,0 +1,188 @@
######################################
## Reporting options
##################################
# Output to this directory
output = CHANGEME
# Toggle mixed content reporting
log-mixed-content = false
# Toggle logging of all external URLs
log-external-urls = false
# Enable extra cache related logging
log-cache-mismatches = false
# Turn off console statistics reporting
#quiet = false
# Increase verbosity of runtime reporting
#verbose = false
######################################
## Crawler user agent options
##################################
# Pretend that 'domain' resolves to 'IP'
#host = domain=IP
# Specify header values that will be send with every request
#header = headername=value
#header = X-Scanner=skipfish
# Specify which one of the pre-defined user agents to use (i|p|f).
user-agent = i
# Set cookie value and send it with every request
#cookie = name1=value1
#cookie = name2=value3
# Reject any new cookies
reject-cookies = false
######################################
## Authentication options
##################################
# Specify the location of the login form
#auth-form = http://example.org/login.php
# Specify the username and password that you want to authenticate
# with. It's advised to use throw away (test) accounts.
#auth-user = myuser
#auth-pass = mypass
# Specify the credential field names when not detected by skipfish.
#auth-user-field = user-field-name
#auth-pass-field = pass-field-name
# The URL to test is the scan is authenticated.
#auth-verify-url = http://example.org/show-profile.php
# In some cases, you might have to specify the location to which the
# form data has to be submitted.
#auth-form-target
# Specify credentials for basic HTTP authentication
#auth = user:pass
######################################
## Crawler scope / depth options
##################################
# Maximum crawl tree depth
max-crawl-depth = 16
# Maximum children to index per node
max-crawl-child = 512
# Maximum descendants to index per branch
max-crawl-descendants = 8192
# Max total number of requests to send
max-request-total = 100000000
# Max requests per second
#max-request-rate = 200
# Node and link crawl probability
crawl-probability = 100
# Repeat probabilistic scan with given seed
#seed = 0xXXXXXX
# Only follow URLs matching 'string'
#include-string = /want/
# Exclude URLs matching 'string'
#exclude-string = /want-not/
# Crawl cross-site links to another domain
#include-domain = scan.also.example.org
# Trust, but do not crawl, another domain
#trust-domain = .google-analytics.com
# Do not parse HTML, etc, to find new links
#no-html-parsing = false
# Do not descend into 5xx locations
skip-error-pages = false
# Add new form auto-fill rule
#form-value = field=value
######################################
## Dictionary management
##################################
# The read-only wordlist that is used for bruteforcing
wordlist = dictionaries/medium.wl
# The read-write wordlist and where learned keywords will be written
# for future scans.
#rw-wordlist = my-wordlist.wl
# Disable extension fuzzing
no-extension-brute = false
# Disable keyword learning
no-keyword-learning = false
######################################
## Performance options
##################################
# Max simultaneous TCP connections, global
max-connections = 40
# Max simultaneous connections, per target IP
max-host-connections = 10
# Max number of consecutive HTTP errors
max-failed-requests = 100
# Total request response timeout
request-timeout = 20
# Individual network I/O timeout
network-timeout = 10
# Timeout on idle HTTP connections
idle-timeout = 10
# Response size limit in bytes
response-size = 400000
# Do not keep binary responses for reporting
discard-binary = true
# Flush request / response data immediately to disk
flush-to-disk = false
# Stop scanning after the given duration h:m:s
#scan-timeout = h:m:s
######################################
## Detection / inject options
##################################
# Specify the signatures file location. To disable signatures, specify /dev/null.
signatures = signatures/signatures.conf
# Enable or disable specific injection tests
#checks-toggle
# Disable all injection tests which means the scan will focus on crawling,
# bruteforcing and passively detect security issues via signatures.
no-injection-tests = false
# Ignore this parameter in the scan
#skip-parameter = search
# Do not submit forms
no-form-submits = false

View File

@ -85,6 +85,32 @@ In a signature that has multiple content strings, static strings can be
mixed with regular expressions. You'll likely get the best performance
by starting with a static string before applying a regular expression.
=== content modifier: regex_match:"<string>"
Regular expressions can capture substrings and with regex_match, it is
possible to compare <string> with the first substring that is returned
by the regular expression.
Given "Apache/2.2.14" as payload and "Apache\/([\d\.]+)" as regex,
you could use regex_match:"2.2.14" to find this specific Apache version.
=== content modifier: nocase
When "nocase" is specified, the content string is matched without case
sensitivity.
This keyword requires no value.
=== header:"<string>"
By default signature matching is performed on the respose body. By
specifying a header name using the "header" keyword, this behavior is
changed: the matching will occur on the header value.
The header name is not case sensitive and header signatures are treated
exactly the same as content signatures meaning that you can use multiple
content strings and their modifiers.
=== mime:"<string>"
The given value will be compared with the MIME type specified by the
@ -133,20 +159,35 @@ errors are related to our tests and report them as such.
=== id:<int>
The unique signature ID. Currently this is for documentation purpose only
but in the future we'll probably add signature chaining which requires
unique ID's as well.
The unique signature ID. This is for documentation purpose and for using
the depend keyword which allows signature chaining.
Note that the signature ID is also included in the report files
(e.g. samples.js).
=== depend:<int>
-----------------------
3. Upcoming keywords
-----------------------
A signature can be made dependent on another signature by specifying it's
signature ID as the value of this keyword. This means that the signature
will be skipped unless the dependent signature was successfully matched
already.
Amongst other changes, it's likely that the next release will have the
following keywords implemented:
One example use case could be a global signature that identifies a
framework, say Wordpress, and dependent signatures that detect wordpress
specific issues.
1) nocase - for case insensitive matching
2) ssl - match against SSL responses only
3) header - match against a specific header
=== proto:"[http|https]"
The "proto" keyword can be used to make a signature only applicable for
either "http" or "https" type URLs.
This changes the default behavior where every signature is applied to
both http and https URLs.
=== report:"[once|always]"
Some signatures are to find host specific problems and only need to be
reported once. This can be acchieved by using report:"once";
This keywords default value is "always".

View File

@ -72,16 +72,14 @@ Performance settings:
\-s s_limit \- response size limit (200000 B)
\-e \- do not keep binary responses for reporting
Safety settings:
Other settings:
\-k duration \- stop scanning after the given duration h:m:s
\--config file \- load specified configuration file
.SH AUTHENTICATION AND ACCESS
.PP
Some sites require authentication, and skipfish supports this in different ways. First there is basic HTTP authentication, for which you can use the \-A flag. Second, and more common, are sites that require authentication on a web application level. For these sites, the best approach is to capture authenticated session cookies and provide them to skipfish using the \-C flag (multiple if needed). Last, you'll need to put some effort in protecting the session from being destroyed by excluding logout links with \-X and/or by rejecting new cookies with \-N.
.IP "-A/--auth <username:password>"
For sites requiring basic HTTP authentication, you can use this flag to specify your credentials.
.IP "-F/--host <ip:hostname>"
Using this flag, you can set the \'\fIHost:\fP\' header value to define a custom mapping between a host and an IP (bypassing the resolver). This feature is particularly useful for not-yet-launched or legacy services that don't have the necessary DNS entries.
@ -97,30 +95,46 @@ This flag allows the user-agent to be specified where \'\fIi\fP\' stands for Int
.IP "-N/--reject-cookies"
This flag causes skipfish to ignore cookies that are being set by the site. This helps to enforce stateless tests and also prevent that cookies set with \'-C\' are not overwritten.
.IP "-A/--auth <username:password>"
For sites requiring basic HTTP authentication, you can use this flag to specify your credentials.
.IP "--auth-form <URL>"
The login form to use with form authentication. By default skipfish will use the form's action URL to submit the credentials. If this is missing than the login data is send to the form URL. In case that is wrong, you can set the form handler URL with --auth-form-target <URL> .
.IP "--auth-user <username>"
The username to be used during form authentication. Skipfish will try to detect the correct form field to use but if it fails to do so (and gives an error), then you can specify the form field name with --auth-user-field.
.IP "--auth-pass <password>"
The password to be used during form authentication. Similar to auth-user, the form field name can (optionally) be set with --auth-pass-field.
.IP "--auth-verify-url <URL>"
This URL allows skipfish to verify whether authentication was successful. This requires a URL where anonymous and authenticated requests are answered with a different response.
.SH CRAWLING SCOPE
.PP
Some sites may be too big to scan in a reasonable timeframe. If the site features well-defined tarpits - for example, 100,000 nearly identical user profiles as a part of a social network - these specific locations can be excluded with -X or -S. In other cases, you may need to resort to other settings: -d limits crawl depth to a specified number of subdirectories; -c limits the number of children per directory; -x limits the total number of descendants per crawl tree branch; and -r limits the total number of requests to send in a scan.
.IP "-d/--max-depth <depth>"
.IP "-d/--max-crawl-depth <depth>"
Limit the depth of subdirectories being crawled (see above).
.IP "-c/--max-child <childs>"
.IP "-c/--max-crawl-child <childs>"
Limit the amount of subdirectories per directory we crawl into (see above).
.IP "-x/--max-descendants <descendants>"
.IP "-x/--max-crawl-descendants <descendants>"
Limit the total number of descendants per crawl tree branch (see above).
.IP "-r/--max-requests <request>"
.IP "-r/--max-request-total <request>"
The maximum number of requests can be limited with this flag.
.IP "-p/--probability <0-100>"
.IP "-p/--crawl-probability <0-100>"
By specifying a percentage between 1 and 100%, it is possible to tell the crawler to follow fewer than 100% of all links, and try fewer than 100% of all dictionary entries. This \- naturally \- limits the completeness of a scan, but unlike most other settings, it does so in a balanced, non-deterministic manner. It is extremely useful when you are setting up time-bound, but periodic assessments of your infrastructure.
.IP "-q/--seed <seed>"
This flag sets the initial random seed for the crawler to a specified value. This can be used to exactly reproduce a previous scan to compare results. Randomness is relied upon most heavily in the -p mode, but also influences a couple of other scan management decisions.
.IP "-I/--include <domain/path>"
.IP "-I/--include-string <domain/path>"
With this flag, you can tell skipfish to only crawl and test URLs that match a certain string. This can help to narrow down the scope of a scan by only whitelisting certain sections of a web site (e.g. \-I /shop).
.IP "-X/--exclude <domain/path>"
.IP "-X/--exclude-string <domain/path>"
The \-X option can be used to exclude files / directories from the scan. This is useful to avoid session termination (i.e. by excluding /logout) or just for speeding up your scans by excluding static content directories like /icons/, /doc/, /manuals/, and other standard, mundane locations along these lines.
.IP "-K/--skip-param <parameter name>"
.IP "-K/--skip-parameter <parameter name>"
This flag allows you to specify parameter names not to fuzz. (useful for applications that put session IDs in the URL, to minimize noise).
.IP "-D/--include-domain <domain>"
@ -132,10 +146,10 @@ In some cases, you do not want to actually crawl a third-party domain, but you t
.IP "-Z/--skip-error-pages"
Do not crawl into pages / directories that give an error 5XX.
.IP "-O/--skip-forms"
.IP "-O/--no-form-submits"
Using this flag will cause forms to be ignored during the scan.
.IP "-P/--ignore-links"
.IP "-P/--no-html-parsing"
This flag will disable link extracting and effectively disables crawling. Using \-P is useful when you want to test one specific URL or when you want to feed skipfish a list of URLs that were collected with an external crawler.
.SH TESTING SCOPE
@ -147,7 +161,7 @@ EXPERIMENTAL: Displays the crawler injection tests. The output shows the index n
.IP "--checks-toggle <check1,check2,..>"
EXPERIMENTAL: Every injection test can be enabled/disabled with using this flag. As value, you need to provide the check numbers which can be obtained with the \-\-checks flag. Multiple checks can be toggled via a comma separated value (i.e. \-\-checks\-toggle 1,2 )
.IP "--no-checks"
.IP "--no-injection-tests"
EXPERIMENTAL: Disables all injection tests for this scan and limits the scan to crawling and, optionally, bruteforcing. As with all scans, the output directory will contain a pivots.txt file. This file can be used to feed future scans.
.SH REPORTING OPTIONS
@ -176,7 +190,7 @@ EXPERIMENTAL: Use this flag to enable runtime reporting of, for example, problem
.SH DICTIONARY MANAGEMENT
.PP
Make sure you've 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.
Make sure you've read the instructions provided in doc/dictionaries.txt to select the right dictionary file and configure it correctly. This step has a profound impact on the quality of scan results later on.
.IP "-S/--wordlist <file>"
Load the specified (read-only) wordlist for use during the scan. This flag is optional but use of a dictionary is highly recommended when performing a blackbox scan as it will highlight hidden files and directories.
@ -187,7 +201,7 @@ Specify an initially empty file for any newly learned site-specific keywords (wh
.IP "-L/--no-keyword-learning"
During the scan, skipfish will try to learn and use new keywords. This flag disables that behavior and should be used when any form of brute-forcing is not desired.
.IP "-Y/--no-ext-fuzzing"
.IP "-Y/--no-extension-brute"
This flag will disable extension guessing during directory bruteforcing.
.IP "-R <age>"
@ -202,7 +216,7 @@ During the scan, a temporary buffer of newly detected keywords is maintained. Th
.SH PERFORMANCE OPTIONS
The default performance setting should be fine for most servers but when the report indicates there were connection problems, you might want to tweak some of the values here. For unstable servers, the scan coverage is likely to improve when using low values for rate and connection flags.
.IP "-l/--max-rate <rate>"
.IP "-l/--max-request-rate <rate>"
This flag can be used to limit the amount of requests per second. This is very useful when the target server can't keep up with the high amount of requests that are generated by skipfish. Keeping the amount requests per second low can also help preventing some rate-based DoS protection mechanisms from kicking in and ruining the scan.
.IP "-g/--max-connections <number>"
@ -211,7 +225,7 @@ The max simultaneous TCP connections (global) can be set with this flag.
.IP "-m/--max-host-connections <number>"
The max simultaneous TCP connections, per target IP, can be set with this flag.
.IP "-f/--max-fail <number>"
.IP "-f/--max-failed-requests <number>"
Controls the maximum number of consecutive HTTP errors you are willing to see before aborting the scan. For large scans, you probably want to set a higher value here.
.IP "-t/--request-timeout <timeout>"
@ -229,7 +243,16 @@ Sets the maximum length of a response to fetch and parse (longer responses will
.IP "-e/--discard-binary"
This prevents binary documents from being kept in memory for reporting purposes, and frees up a lot of RAM.
.IP "--flush-to-disk"
This causes request / response data to be flushed to disk instead of being kept in memory. As a result, the memory usage for large scans will be significant lower.
.SH EXAMPLES
\fBScan type: config\fP
.br
skipfish \-\-config config/example.conf http://example.com
.br
.br
\fBScan type: quick\fP
.br
skipfish \-o output/dir/ http://example.com

View File

@ -5,14 +5,27 @@
# default.
# A phpinfo() page
id:11001; sev:3; content:"<title>phpinfo()</title><meta name="; depth:2048; memo:"phpinfo() page";
id:11001; sev:3; content:"<title>phpinfo()</title><meta name="; depth:2048; \
memo:"phpinfo() page";
# A phpmyadmin page
id:11002; sev:3; content:'<title>phpMyAdmin </title>'; depth:1024; content:'<a href="http://www.phpmyadmin.net" target="_blank" class="logo">'; depth:2048; memo:"phpMyAdmin";
id:11002; sev:3; content:'<title>phpMyAdmin </title>'; depth:1024; \
content:'<a href="http://www.phpmyadmin.net" target="_blank" class="logo">'; depth:2048; \
memo:"phpMyAdmin detected";
id:11003; sev:3; content:"<title>Parallels Plesk Panel"; depth:1024; content:'action="/login_up.php3" method="post"'; memo:"Plesk administrative interface";
# Plesk admin interface
id:11003; sev:3; content:"<title>Parallels Plesk Panel"; depth:1024; \
content:'action="/login_up.php3" method="post"'; \
memo:"Plesk administrative interface";
# Reference: http://httpd.apache.org/docs/2.2/mod/mod_status.html
id:11004; sev:3; mime:"text/html"; content:"<title>Apache Status</title>"; depth:100; content:"<h1>Apache Server Status for"; depth:25; memo:"Apache mod_status page";
id:11004; sev:3; mime:"text/html"; \
content:"<title>Apache Status</title>"; depth:100; \
content:"<h1>Apache Server Status for"; depth:25; \
memo:"Apache mod_status page detected";
id:11005; sev:3; mime:"text/html"; content:"<title>Server Information</title>"; depth:200; content:"Apache Server Information</h1>"; depth:50; memo:"Apache mod_status page";
# Reference: http://httpd.apache.org/docs/2.2/mod/mod_info.html
id:11005; sev:3; mime:"text/html"; \
content:"<title>Server Information</title>"; depth:200; \
content:"Apache Server Information</h1>"; depth:50; \
memo:"Apache mod_status page detected";

View File

@ -3,47 +3,113 @@
# INTERESTING PAGES / FILES
# Detect private keys
id:31001; sev:2; mime:"text/plain"; content:"-----BEGIN DSA PRIVATE KEY-----"; depth:100; memo:"DSA private key";
id:31002; sev:2; mime:"text/plain"; content:"-----BEGIN RSA PRIVATE KEY-----"; depth:100; memo:"RSA private key";
id:31001; sev:2; memo:"DSA private key"; \
mime:"text/plain"; \
content:"-----BEGIN DSA PRIVATE KEY-----"; depth:100;
id:31002; sev:2; memo:"RSA private key"; \
mime:"text/plain"; \
content:"-----BEGIN RSA PRIVATE KEY-----"; depth:100;
id:31003; sev:3; content:'ADDRESS=(PROTOCOL='; memo:"SQL configuration or logs";
id:31004; sev:3; content:";pwd="; content:";database="; depth:512; memo:"ODBC connect string";
id:31005; sev:3; content:"Data Source="; content:";Password="; depth:512; memo:"ODBC connect string";
id:31006; sev:3; content:"Provider="; content:";Password="; depth:512; memo:"ODBC connect string";
id:31007; sev:3; content:"Driver="; content:";Pwd="; depth:512; memo:"ODBC connect string";
# SQL credentials
id:31003; sev:3; memo:"SQL configuration or logs"; \
content:'ADDRESS=(PROTOCOL=';
id:31004; sev:3; memo:"ODBC connect string"; \
content:";pwd="; \
content:";database="; depth:512;
id:31005; sev:3; memo:"ODBC connect string"; \
content:"Data Source="; \
content:";Password="; depth:512;
id:31006; sev:3; memo:"ODBC connect string"; \
content:"Provider="; \
content:";Password="; depth:512;
id:31007; sev:3; memo:"ODBC connect string"; \
content:"Driver="; \
content:";Pwd="; depth:512;
# Typical crossdomain / access policy files
id:31008; sev:2; content:"<cross-domain-policy>"; depth:512; content:'<allow-access-from domain="*"'; depth:50; memo:"Flash cross-domain policy with wildcard";
id:31009; sev:4; content:"<access-policy>"; depth:512; content:'<domain uri="*"/>'; depth:512; memo:"Silverlight cross-domain policy with wildcard";
# Crossdomain & access policy files
id:31008; sev:2; memo:"Flash cross-domain policy with wildcard"; \
content:"<cross-domain-policy>"; depth:512; \
content:'<allow-access-from domain="*"'; depth:50;
id:31009; sev:4; memo:"Silverlight cross-domain policy with wildcard"; \
content:"<access-policy>"; depth:512; \
content:'<domain uri="*"/>'; depth:512;
# Web.xml config file
id:31010; sev:3; content:"<web-app"; depth:512; memo:"web.xml config file";
id:31010; sev:3; memo:"web.xml config file"; \
content:"<web-app"; depth:512;
# SVN RCS data
id:31011; sev:3; content:"svn:special svn"; depth:256; memo:"SVN RCS data";
id:31012; sev:3; content:"SVN RCS data"; depth:256; memo:"SVN RCS data";
id:31011; sev:3; memo:"SVN RCS data"; \
mime:"text/plain"; \
content:"svn:special svn"; depth:256;
id:31012; sev:3; memo:"SVN RCS data"; \
mime:"text/plain"; \
content:"SVN RCS data"; depth:256;
id:31018; sev:3; memo:"SVN entries file"; \
mime:"text/plain"; \
content:"10"; depth:1; \
content:"dir"; depth:4;
# Log files
id:31013; sev:3; content:"0] \"GET /"; depth:1024; memo:"Apache access log";
id:31014; sev:3; content:"[error] [client "; depth:1024; memo:"Apache error log";
id:31015; sev:3; content:"0, GET, /"; depth:1024; memo:"Microsoft IIS access log";
id:31013; sev:3; memo:"Apache access log"; \
content:"0] \"GET /"; depth:1024;
id:31014; sev:3; memo:"Apache error log"; \
content:"[error] [client "; depth:1024;
id:31015; sev:3; memo:"Microsoft IIS access log"; \
content:"0, GET, /"; depth:1024;
# Generic robots.txt file
id:31016; sev:4; content:"User-agent:"; depth:100; content:"Disallow: /"; memo:"robots.txt file";
id:31016; sev:4; memo:"robots.txt file"; \
content:"User-agent:"; depth:100; \
content:"Disallow: /";
# Libcurl cookie files are created by some PHP apps (e.g. wordpress plugins)
id:31017; sev:3; memo:"libcurl cookie jar"; \
mime:"text/plain"; \
content:"# Netscape HTTP Cookie File"; depth:10; \
content:"# This file was generated by libcurl"; depth:200;
# Signatures to detect SQL dumps
id:31101; sev:2; mime:"text/plain"; content:"-- MySQL dump"; depth:1; content:"-- Host"; depth:256; content:"-- Server version"; memo:"MySQL dump database file";
id:31103; sev:2; mime:"text/plain"; content:" phpMyAdmin SQL Dump"; depth:3; content:" version"; content:"CREATE TABLE"; memo:"phpMyAdmin database dump file";
id:31101; sev:2; memo:"MySQL dump database file"; \
mime:"text/plain"; \
content:"-- MySQL dump"; depth:1; \
content:"-- Host"; depth:256; \
content:"-- Server version";
id:31103; sev:2; memo:"phpMyAdmin database dump file"; \
mime:"text/plain"; \
content:" phpMyAdmin SQL Dump"; depth:3; \
content:" version"; \
content:"CREATE TABLE";
id:31104; sev:2; memo:"SQL script detected"; \
mime:"text/plain"; \
content:"DECLARE @"; \
content:"SET @"; \
content:"(SELECT|INSERT|DELETE|BACKUP|CREATE)"; type:"regex"; depth:1024;
# Source code and scripts
id:32001; sev:3; content:"\nimport java."; depth:512; memo:"Java source";
id:32002; sev:3; content:"\n#include"; depth:512; memo:"C/C++ source";
id:32003; sev:3; content:"#!/"; depth:1; memo:"Shell script";
id:32004; sev:3; content:!"# ?>" content:!"<?import"; content:"<?"; content:!"xml"; depth:1; content:"?>"; memo:"PHP source";
id:32005; sev:3; content:"<%@"; content:"%>"; memo:"JSP source";
id:32006; sev:3; content:"<%"; content:"%>"; memo:"ASP source";
id:32001; sev:3; memo:"Java source"; \
content:"\nimport java."; depth:512;
id:32002; sev:3; memo:"C/C++ source"; \
content:"\n#include"; depth:512;
id:32003; sev:3; memo:"Shell script"; \
content:"#!/"; depth:1;
id:32004; sev:3; memo:"PHP source"; \
content:!"# ?>"; \
content:!"<?import"; \
content:"<?"; \
content:!"xml"; depth:1; \
content:"?>";
id:32005; sev:3; memo:"JSP source"; \
content:"<%@"; \
content:"%>";
id:32006; sev:3; memo:"ASP source"; \
content:"<%"; \
content:"%>";
# These two need to be improved!
id:32007; sev:3; content:"@echo "; depth:256; memo:"DOS batch script";
id:32008; sev:3; content:"(\"Wscript."; depth:256; memo:"Windows shell script";
id:32007; sev:3; memo:"DOS batch script"; \
content:"@echo "; depth:256;
id:32008; sev:3; memo:"Windows shell script"; \
content:"(\"Wscript."; depth:256;

View File

@ -18,16 +18,24 @@ id:21012; prob:40402; content:"Incorrect syntax near"; memo:"SQL syntax error";
id:21013; prob:40402; content:"[DM_QUERY_E_SYNTAX]"; memo:"SQL syntax error";
# Stacktraces and server errors
id:22001; prob:40402; content:"<span><H1>Server Error in '"; memo:"ASP.NET Yellow Screen of Death";
id:22002; prob:40402; content:"<font face=\"Arial\" size=2>error '"; memo:"Microsoft runtime error";
id:22003; prob:40402; content:"[an error occurred while processing"; memo:"SHTML error";
id:22004; prob:40402; content:"Traceback (most recent call last):"; memo:"Python error";
id:22005; prob:40402; content:"<title>JRun Servlet Error</title>"; memo:"JRun servlet error";
# Java exceptions
id:22006; prob:40402; content:"Stacktrace:"; content:"javax.servlet."; content:"<b>note</b> <u>The full stack trace"; memo:"Java server stacktrace";
id:22007; prob:40402; content:"at java.lang.Thread.run"; content:".java:"; memo:"Java runtime stacktrace";
id:22020; prob:40402; content:"<b>type</b> Exception report</p><p>"; content:"<p><b>description</b> <u>The server "; depth:512; memo:"Java server exception";
id:22006; prob:40402; content:"Stacktrace:"; \
content:"javax.servlet."; \
content:"<b>note</b> <u>The full stack trace"; \
memo:"Java server stacktrace";
id:22007; prob:40402; content:"at java.lang.Thread.run"; \
content:".java:"; \
memo:"Java runtime stacktrace";
id:22020; prob:40402; content:"<b>type</b> Exception report</p><p>"; \
content:"<p><b>description</b> <u>The server "; depth:512; \
memo:"Java server exception";
# PHP HTML and text errors. The text and HTML sigs can perhaps be merged,
id:22008; prob:40402; content:"<b>Fatal error</b>: "; content:"</b> on line <b>"; depth:512; memo:"PHP error (HTML)";

View File

@ -30,6 +30,7 @@
#include "crawler.h"
#include "analysis.h"
#include "signatures.h"
#include "report.h"
#include "pcre.h"
u8 no_parse, /* Disable HTML link detection */
@ -325,13 +326,19 @@ static u8* html_decode_param(u8* url, u8 also_js) {
/* Macro to find and move past parameter name (saves result in
_store, NULL if not found). Buffer needs to be NUL-terminated
at nearest >. */
at nearest >. The macro supports the "name\s*=aaa" cases. */
#define FIND_AND_MOVE(_store, _val, _param) { \
(_store) = inl_strcasestr((u8*)_val, (u8*)_param); \
if (_store) { \
if (!isspace((_store)[-1])) (_store) = NULL; \
else (_store) += strlen((char*)_param); \
if (!isspace((_store)[-1])) { \
(_store) = NULL; \
} else { \
(_store) += strlen((char*)_param); \
while (_store && isspace((_store)[0])) (_store)++; \
if (_store && (_store)[0] == '=') (_store)++; \
else (_store) = NULL; \
} \
} \
} while (0)
@ -480,9 +487,9 @@ void collect_form_data(struct http_request* req,
u8 *tag_name, *tag_value, *tag_type, *clean_name = NULL,
*clean_value = NULL;
FIND_AND_MOVE(tag_name, cur_str, "name=");
FIND_AND_MOVE(tag_value, cur_str, "value=");
FIND_AND_MOVE(tag_type, cur_str, "type=");
FIND_AND_MOVE(tag_name, cur_str, "name");
FIND_AND_MOVE(tag_value, cur_str, "value");
FIND_AND_MOVE(tag_type, cur_str, "type");
if (!tag_name) goto next_tag;
@ -494,6 +501,7 @@ void collect_form_data(struct http_request* req,
if (tag_value) {
EXTRACT_ALLOC_VAL(tag_value, tag_value);
clean_value = html_decode_param(tag_value, 0);
ck_free(tag_value);
tag_value = 0;
}
@ -585,7 +593,7 @@ final_checks:
if (pass_form) {
if (warn_mixed && (req->proto != PROTO_HTTPS || orig_req->proto != PROTO_HTTPS))
if (warn_mixed && (req->proto != PROTO_HTTPS || orig_req->proto != PROTO_HTTPS))
problem(PROB_PASS_NOSSL, req, orig_res, NULL, req->pivot, 0);
else
problem(PROB_PASS_FORM, req, orig_res, NULL, req->pivot, 0);
@ -643,6 +651,7 @@ static u8 is_mostly_ascii(struct http_response* res) {
struct http_request* make_form_req(struct http_request *req,
struct http_request *base,
struct http_response *res,
u8* cur_str, u8* target) {
u8 *method, *clean_url;
@ -650,8 +659,8 @@ struct http_request* make_form_req(struct http_request *req,
struct http_request* n;
u8 parse_form = 1;
FIND_AND_MOVE(dirty_url, cur_str, "action=");
FIND_AND_MOVE(method, cur_str, "method=");
FIND_AND_MOVE(dirty_url, cur_str, "action");
FIND_AND_MOVE(method, cur_str, "method");
/* See if we need to POST this form or not. */
@ -677,6 +686,9 @@ struct http_request* make_form_req(struct http_request *req,
clean_url = html_decode_param(dirty_url, 0);
ck_free(dirty_url);
/* Add to the pivot's so we can test it later */
test_add_link(clean_url, base ? base : req, res, 4, 1);
n = ck_alloc(sizeof(struct http_request));
n->pivot = req->pivot;
@ -769,7 +781,7 @@ void scrape_response(struct http_request* req, struct http_response* res) {
if (ISTAG(cur_str, "meta")) {
link_type = 1;
FIND_AND_MOVE(dirty_url, cur_str, "content=");
FIND_AND_MOVE(dirty_url, cur_str, "content");
if (dirty_url) {
EXTRACT_ALLOC_VAL(meta_url, dirty_url);
@ -780,42 +792,42 @@ void scrape_response(struct http_request* req, struct http_response* res) {
} else if (ISTAG(cur_str, "img")) {
link_type = 2;
FIND_AND_MOVE(dirty_url, cur_str, "src=");
FIND_AND_MOVE(dirty_url, cur_str, "src");
} else if (ISTAG(cur_str, "object") || ISTAG(cur_str, "embed") ||
ISTAG(cur_str, "applet") || ISTAG(cur_str, "iframe") ||
ISTAG(cur_str, "frame")) {
link_type = 3;
FIND_AND_MOVE(dirty_url, cur_str, "src=");
if (!dirty_url) FIND_AND_MOVE(dirty_url, cur_str, "codebase=");
FIND_AND_MOVE(dirty_url, cur_str, "src");
if (!dirty_url) FIND_AND_MOVE(dirty_url, cur_str, "codebase");
} else if (ISTAG(cur_str, "param") && inl_strcasestr(cur_str,
(u8*)"movie")) {
link_type = 3;
FIND_AND_MOVE(dirty_url, cur_str, "value=");
FIND_AND_MOVE(dirty_url, cur_str, "value");
} else if (ISTAG(cur_str, "script")) {
link_type = 4;
FIND_AND_MOVE(dirty_url, cur_str, "src=");
FIND_AND_MOVE(dirty_url, cur_str, "src");
} else if (ISTAG(cur_str, "link") && inl_strcasestr(cur_str,
(u8*)"stylesheet")) {
link_type = 4;
FIND_AND_MOVE(dirty_url, cur_str, "href=");
FIND_AND_MOVE(dirty_url, cur_str, "href");
} else if (ISTAG(cur_str, "base")) {
set_base = 1;
FIND_AND_MOVE(dirty_url, cur_str, "href=");
FIND_AND_MOVE(dirty_url, cur_str, "href");
} else if (ISTAG(cur_str, "form")) {
/* Parse the form and kick off a new pivot for further testing */
struct http_request* n = make_form_req(req, base, cur_str, NULL);
struct http_request* n = make_form_req(req, base, res, cur_str, NULL);
if (n) {
if (url_allowed(n) && R(100) < crawl_prob && !no_forms) {
is_post = (n->method && !strcmp((char*)n->method, "POST"));
@ -831,8 +843,8 @@ void scrape_response(struct http_request* req, struct http_response* res) {
/* All other tags - other <link> types, <a>, <bgsound> -
are handled in a generic way. */
FIND_AND_MOVE(dirty_url, cur_str, "href=");
if (!dirty_url) FIND_AND_MOVE(dirty_url, cur_str, "src=");
FIND_AND_MOVE(dirty_url, cur_str, "href");
if (!dirty_url) FIND_AND_MOVE(dirty_url, cur_str, "src");
}
@ -1300,8 +1312,8 @@ static void check_js_xss(struct http_request* req, struct http_response* res,
if ((!prefix(last_word, "innerHTML") ||
!prefix(last_word, "open") ||
!prefix(last_word, "url") ||
!prefix(last_word, "href") ||
!case_prefix(last_word, "url") ||
!case_prefix(last_word, "href") ||
!prefix(last_word, "write")) &&
(!case_prefix(text + 1,"//skipfish.invalid/") ||
!case_prefix(text + 1,"http://skipfish.invalid/") ||
@ -1318,9 +1330,9 @@ static void check_js_xss(struct http_request* req, struct http_response* res,
!case_prefix(end_quote + 1,"fish\"\"\"")))
problem(PROB_URL_XSS, req, res, (u8*)"injected string in JS/CSS code (quote escaping issue)", req->pivot, 0);
if(end_quote && (!prefix(last_word, "on") ||
!prefix(last_word, "url") ||
!prefix(last_word, "href")) &&
if(end_quote && (!case_prefix(last_word, "on") ||
!case_prefix(last_word, "url") ||
!case_prefix(last_word, "href")) &&
(!case_prefix(end_quote + 1,"skip&apos;&apos;&apos;") ||
!case_prefix(end_quote + 1,"skip&#x27;&#x27;&#x27;") ||
!case_prefix(end_quote + 1,"skip&quot;&quot;&quot;") ||
@ -1469,6 +1481,10 @@ u8 content_checks(struct http_request* req, struct http_response* res) {
DEBUG_CALLBACK(req, res);
/* Return immediately if there is no meat */
if (!res->payload || !res->pay_len)
return 0;
/* CHECK 0: signature matching */
match_signatures(req, res);
@ -1701,9 +1717,35 @@ u8 content_checks(struct http_request* req, struct http_response* res) {
if ((!strcasecmp((char*)param_name, "href") ||
!strcasecmp((char*)param_name, "src") ||
!strcasecmp((char*)param_name, "action") ||
(!strcasecmp((char*)param_name, "value") &&
strcasecmp((char*)tag_name, "input")) ||
!strcasecmp((char*)param_name, "codebase")) && clean_val) {
!strcasecmp((char*)param_name, "cite") ||
!strcasecmp((char*)param_name, "longdesc") ||
/* <object classid data codebase */
(!strcasecmp((char*)tag_name, "object") &&
(!strcasecmp((char*)param_name, "classid") ||
!strcasecmp((char*)param_name, "data"))) ||
/* html5 formaction */
((!strcasecmp((char*)tag_name, "button") ||
!strcasecmp((char*)tag_name, "input")) &&
!strcasecmp((char*)param_name, "formaction")) ||
/* html5 <html manifest= */
(!strcasecmp((char*)tag_name, "html") &&
!strcasecmp((char*)param_name, "manifest")) ||
/* html5 <video poster= */
(!strcasecmp((char*)tag_name, "video") &&
!strcasecmp((char*)param_name, "poster")) ||
/* <head profile= */
(!strcasecmp((char*)tag_name, "head") &&
!strcasecmp((char*)param_name, "profile")) ||
/* <applet/object codebase= */
((!strcasecmp((char*)tag_name, "object") ||
!strcasecmp((char*)tag_name, "applet")) &&
!strcasecmp((char*)param_name, "codebase")))
&& clean_val) {
/* Check links with the javascript scheme */
if (!case_prefix(clean_val, "javascript:") ||
@ -1746,15 +1788,19 @@ u8 content_checks(struct http_request* req, struct http_response* res) {
if (*url == '\'' || *url == '"') { url++; semi_safe = 1; }
if (!case_prefix(url, "http://skipfish.invalid/") ||
!case_prefix(url, "//skipfish.invalid/"))
!case_prefix(url, "//skipfish.invalid/") ||
!case_prefix(url, "skipfish:"))
problem(PROB_URL_REDIR, req, res, (u8*)"injected URL in META refresh",
req->pivot, 0);
/* Unescaped semicolon in Refresh headers is unsafe with MSIE6. */
/* Unescaped semicolon in Refresh headers is unsafe with
MSIE6: injecting an extra URL with javascript: scheme can
lead to an XSS */
if (!case_prefix(url, "skipfish:") ||
(!semi_safe && strchr((char*)url, ';')))
problem(PROB_URL_XSS, req, res, (u8*)"injected URL in META refresh",
if ((strstr((char*)url, "/invalid/;") ||
inl_strcasestr(url, (u8*)"%2finvalid%2f;")) &&
!semi_safe)
problem(PROB_URL_REDIR, req, res, (u8*)"unescaped semi-colon in META refresh (IE6)",
req->pivot, 0);
} else {
@ -2467,24 +2513,34 @@ static void check_for_stuff(struct http_request* req,
}
/* Deletes payload of binary responses if requested. This is called when pivot
enters PSTATE_DONE. */
/* This is called when pivot enters PSTATE_DONE. It deletes payload of
binary responses if requested and can flush pivot payloads to disk. */
void maybe_delete_payload(struct pivot_desc* pv) {
u8 tmp[64];
u32 i;
if (pv->res && pv->res->pay_len > 256 && !is_mostly_ascii(pv->res)) {
/* Return if there is nothing we should do */
if (!delete_bin && !flush_pivot_data)
return;
/* Delete binary payload when desired */
if (delete_bin && pv->res && pv->res->pay_len > 256 &&
!is_mostly_ascii(pv->res)) {
ck_free(pv->res->payload);
sprintf((char*)tmp, "[Deleted binary payload (%u bytes)]", pv->res->pay_len);
pv->res->payload = ck_strdup(tmp);
pv->res->pay_len = strlen((char*)tmp);
}
/* Flush issue req/res payloads */
if (flush_pivot_data)
flush_payload(pv->req, pv->res);
for (i=0;i<pv->issue_cnt;i++) {
if (pv->issue[i].res && pv->issue[i].res->pay_len > 256 &&
if (delete_bin && pv->issue[i].res && pv->issue[i].res->pay_len > 256 &&
!is_mostly_ascii(pv->issue[i].res)) {
ck_free(pv->issue[i].res->payload);
sprintf((char*)tmp, "[Deleted binary payload (%u bytes)]",
@ -2493,6 +2549,9 @@ void maybe_delete_payload(struct pivot_desc* pv) {
pv->issue[i].res->pay_len = strlen((char*)tmp);
}
}
/* Flush pivot req/res payloads */
if (flush_pivot_data)
flush_payload(pv->issue[i].req, pv->issue[i].res);
}
}

View File

@ -91,6 +91,7 @@ void collect_form_data(struct http_request* req,
struct http_request* make_form_req(struct http_request *req,
struct http_request *base,
struct http_response* res,
u8* cur_str, u8* target);
/* MIME detector output codes: */

View File

@ -74,6 +74,33 @@ void authenticate() {
}
/* Helper function to find form fields to put the credentials in */
static u8 find_and_set_field(struct param_array *par, const char **test_fields,
u32 par_type, u8* par_value) {
u32 i, k;
/* Try to find the field */
for (i=0; i<par->c; i++) {
if (!par->n[i] || par->t[i] != par_type) continue;
/* Match it with the strings */
for (k=0; test_fields[k]; k++) {
if (inl_strcasestr(par->n[i], (u8*)test_fields[k])) {
DEBUGC(L1, "*-- Authentication - found login field: %s\n", par->n[i]);
if (par->v[i]) ck_free(par->v[i]);
par->v[i] = ck_strdup(par_value);
return 1;
}
}
}
/* None found..*/
return 0;
}
/* Main function to submit the authentication, login form. This function
will try find the right form and , unless form fields are specified on
command-line, try to find the right fields in order to store the username
@ -82,26 +109,29 @@ void authenticate() {
u8 submit_auth_form(struct http_request* req,
struct http_response* res) {
u8* form;
u8 *form, *form_ptr;
u8 *vurl = NULL;
u8 is_post = 1;
u8 par_type = PARAM_POST;
u32 i = 0, k = 0;
struct http_request* n = NULL;
DEBUG_CALLBACK(req, res);
/* Loop over the forms till we get our password form */
form_ptr = res->payload;
do {
form = inl_strcasestr(res->payload, (u8*)"<form");
form = inl_strcasestr(form_ptr, (u8*)"<form");
if (!form) break;
/* Update the pointer for the next form */
form_ptr += 5;
if (auth_form_target)
vurl = ck_strdup(auth_form_target);
n = make_form_req(req, NULL, form, vurl);
n = make_form_req(req, NULL, res, form, vurl);
if (!n)
FATAL("No auth form found\n");
@ -118,45 +148,29 @@ u8 submit_auth_form(struct http_request* req,
to find one by using the strings from the "user_fields" array
(defined in auth.h).
*/
if (auth_user_field)
if (auth_user_field) {
if(!get_value(par_type, auth_user_field, 0, &n->par))
continue;
if (auth_pass_field)
set_value(par_type, auth_user_field, ck_strdup(auth_user), 0, &n->par);
DEBUGC(L1, "*-- Authentication - auth_user field set (%s)\n",
auth_user_field);
} else if (!find_and_set_field(&n->par, user_fields, par_type, auth_user)) {
continue;
}
if (auth_pass_field) {
if(!get_value(par_type, auth_pass_field, 0, &n->par))
continue;
/* Try to find a user name-like field */
for (i=0; i<n->par.c; i++) {
if (!n->par.n[i] || n->par.t[i] != par_type) continue;
/* Find and set the user field */
for (k=0; !auth_user_field && user_fields[k]; k++) {
if (inl_strcasestr(n->par.n[i], (u8*)user_fields[k])) {
DEBUGC(L1, "*-- Authentication - using user field: %s\n", n->par.n[i]);
if (n->par.v[i]) ck_free(n->par.v[i]);
n->par.v[i] = ck_strdup(auth_user);
auth_user_field = n->par.n[i];
break;
}
}
/* Find and set the password field */
for (k=0; !auth_pass_field && pass_fields[k]; k++) {
if (inl_strcasestr(n->par.n[i], (u8*)pass_fields[k])) {
DEBUGC(L1, "*-- Authentication - using pass field: %s\n", n->par.n[i]);
if (n->par.v[i]) ck_free(n->par.v[i]);
n->par.v[i] = ck_strdup(auth_pass);
auth_pass_field = n->par.n[i];
break;
}
}
set_value(par_type, auth_pass_field, ck_strdup(auth_pass), 0, &n->par);
DEBUGC(L1, "*-- Authentication - auth_pass field set (%s)\n",
auth_pass_field);
} else if (!find_and_set_field(&n->par, pass_fields, par_type, auth_pass)) {
continue;
}
/* If one of both fields is not set, there is no point in submitting
so we'll look for another form in the page */
if (!auth_pass_field || !auth_user_field)
continue;
/* If we get here: credentials are set */
n->callback = auth_form_callback;
DEBUGC(L1, "*-- Submitting authentication form\n");
#ifdef LOG_STDERR
@ -165,6 +179,7 @@ u8 submit_auth_form(struct http_request* req,
async_request(n);
auth_state = ASTATE_SEND;
break;
} while (form);
if (auth_state != ASTATE_SEND)

View File

@ -53,8 +53,11 @@ static u8 inject_diff_shell_check(struct http_request*, struct http_response*);
static u8 inject_dir_listing_tests(struct pivot_desc* pivot);
static u8 inject_dir_listing_check(struct http_request*, struct http_response*);
static u8 inject_inclusion_tests(struct pivot_desc* pivot);
static u8 inject_inclusion_check(struct http_request*, struct http_response*);
static u8 inject_lfi_tests(struct pivot_desc* pivot);
static u8 inject_lfi_check(struct http_request*, struct http_response*);
static u8 inject_rfi_tests(struct pivot_desc* pivot);
static u8 inject_rfi_check(struct http_request*, struct http_response*);
static u8 inject_split_tests(struct pivot_desc* pivot);
static u8 inject_split_check(struct http_request*, struct http_response*);
@ -80,6 +83,9 @@ static u8 inject_behavior_check(struct http_request*, struct http_response*);
static u8 param_behavior_tests(struct pivot_desc* pivot);
static u8 param_behavior_check(struct http_request*, struct http_response*);
static u8 agent_behavior_tests(struct pivot_desc* pivot);
static u8 agent_behavior_check(struct http_request*, struct http_response*);
static u8 param_ognl_tests(struct pivot_desc* pivot);
static u8 param_ognl_check(struct http_request*, struct http_response*);
@ -111,7 +117,7 @@ static u8 xssi_check(struct http_request*, struct http_response*);
*/
u32 cb_handle_cnt = 19; /* Total of checks */
u32 cb_handle_cnt = 21; /* Total of checks */
u32 cb_handle_off = 4; /* Checks after the offset are optional */
static struct cb_handle cb_handles[] = {
@ -138,6 +144,10 @@ static struct cb_handle cb_handles[] = {
CHK_IPS, (u8*)"IPS check",
dir_ips_tests, dir_ips_check, 0 },
{ 3, 1, 0, 0, 0, PIVOT_DIR|PIVOT_FILE,
CHK_AGENT, (u8*)"User agent behavior",
agent_behavior_tests, agent_behavior_check, 0 },
{ 2, 1, 0, 0, 0, PIVOT_DIR|PIVOT_SERV,
CHK_PUT, (u8*)"PUT upload",
put_upload_tests, put_upload_check, 0 },
@ -146,15 +156,13 @@ static struct cb_handle cb_handles[] = {
CHK_DIR_LIST, (u8*)"dir traversal",
inject_dir_listing_tests, inject_dir_listing_check, 0 },
#ifdef RFI_SUPPORT
{ 12, 1, 1, 0, 1, 0,
CHK_FI, (u8*)"file inclusion",
inject_inclusion_tests, inject_inclusion_check, 0 },
#else
{ 11, 1, 1, 0, 1, 0,
CHK_FI, (u8*)"file inclusion",
inject_inclusion_tests, inject_inclusion_check, 0 },
#endif
{ 48, 1, 1, 0, 1, 0,
CHK_LFI, (u8*)"local file inclusion",
inject_lfi_tests, inject_lfi_check, 0 },
{ 1, 0, 1, 0, 1, 0,
CHK_RFI, (u8*)"remote file inclusion",
inject_rfi_tests, inject_rfi_check, 0 },
{ 4, 0, 1, 0, 1, 0,
CHK_XSS, (u8*)"XSS injection",
@ -164,7 +172,7 @@ static struct cb_handle cb_handles[] = {
CHK_XSSI, (u8*)"XSSI protection",
xssi_tests, xssi_check, 0 },
{ 0, 0, 1, 0, 1, 0,
{ 1, 0, 1, 0, 1, 0,
CHK_PROLOG, (u8*)"prologue injection",
inject_prologue_tests, inject_prologue_check, 0 },
@ -326,6 +334,7 @@ u8 inject_state_manager(struct http_request* req, struct http_response* res) {
if (cb_handles[check].res_keep) {
for (i=0; i<req->pivot->misc_cnt; i++) {
if (!MREQ(i) || !MRES(i)) {
DEBUG("-- Misc request #%d failed\n", i);
problem(PROB_FETCH_FAIL, req, res, (u8*)"During injection testing", req->pivot, 0);
/* Today, we'll give up on this test. In the next release: reschedule */
@ -1166,10 +1175,11 @@ static u8 inject_dir_listing_check(struct http_request* req,
}
static u8 inject_inclusion_tests(struct pivot_desc* pivot) {
static u8 inject_lfi_tests(struct pivot_desc* pivot) {
struct http_request* n;
u32 i;
u32 i, v;
u32 count = 0;
/* Perhaps do this in state manager ?*/
if (pivot->state == PSTATE_CHILD_INJECT)
@ -1179,134 +1189,242 @@ static u8 inject_inclusion_tests(struct pivot_desc* pivot) {
the checks are almost identical */
i = 0;
while (disclosure_tests[i]) {
while (i <= MAX_LFI_INDEX) {
v = 0;
while (lfi_tests[i].vectors[v]) {
/* ONE: no encoding */
n = req_copy(pivot->req, pivot, 1);
n->fuzz_par_enc = (u8*)ENC_NULL;
ck_free(TPAR(n));
TPAR(n) = ck_strdup((u8*)lfi_tests[i].vectors[v]);
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
/* TWO: path encoding used */
n = req_copy(pivot->req, pivot, 1);
n->fuzz_par_enc = (u8*)ENC_PATH;
ck_free(TPAR(n));
TPAR(n) = ck_strdup((u8*)lfi_tests[i].vectors[v]);
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
/* THREE: double path encoding */
n = req_copy(pivot->req, pivot, 1);
n->fuzz_par_enc = (u8*)ENC_PATH;
ck_free(TPAR(n));
TPAR(n) = url_encode_token((u8*)lfi_tests[i].vectors[v],
strlen(lfi_tests[i].vectors[v]),
(u8*)ENC_PATH);
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
/* FOUR: path encoding, with NULL byte and extension */
n = req_copy(pivot->req, pivot, 1);
ck_free(TPAR(n));
u8 *tmp = url_encode_token((u8*)lfi_tests[i].vectors[v],
strlen(lfi_tests[i].vectors[v]),
(u8*)ENC_PATH);
TPAR(n) = ck_alloc(strlen((char*)tmp) + 10);
sprintf((char*)TPAR(n), "%s%%00%%2ejs", (char*)tmp);
ck_free(tmp);
n->fuzz_par_enc = (u8*)ENC_NULL;
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
v++;
}
i++;
}
u8 *web1 = ck_strdup((u8*)"/WEB-INF/web.xml");
u8 *web2 = ck_strdup((u8*)"/WEB-INF%2fweb%2exml");
u8 *web3 = ck_strdup((u8*)"/WEB-INF%2fweb%2exml%00.js");
for (v=0; v<8; v++) {
PREFIX_STRING(web1, "/..");
PREFIX_STRING(web2, "%2f%2e%2e");
PREFIX_STRING(web3, "%2f%2e%2e");
DEBUG("WEB: %s\n", web1);
n = req_copy(pivot->req, pivot, 1);
n->fuzz_par_enc = (u8*)ENC_NULL;
ck_free(TPAR(n));
TPAR(n) = ck_strdup(web1);
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
n = req_copy(pivot->req, pivot, 1);
/* No % encoding for these requests */
n->fuzz_par_enc = (u8*)ENC_NULL;
ck_free(TPAR(n));
TPAR(n) = ck_strdup((u8*)disclosure_tests[i]);
TPAR(n) = ck_strdup(web2);
n->callback = inject_state_manager;
n->user_val = i;
n->check_subid = i;
n->user_val = count++;
async_request(n);
n = req_copy(pivot->req, pivot, 1);
/* No % encoding for these requests */
n->fuzz_par_enc = (u8*)ENC_NULL;
ck_free(TPAR(n));
TPAR(n) = ck_strdup(web3);
n->callback = inject_state_manager;
n->check_subid = i;
n->user_val = count++;
async_request(n);
i++;
}
#ifdef RFI_SUPPORT
/* Optionally try RFI */
ck_free(web1);
ck_free(web2);
ck_free(web3);
pivot->pending = count;
return 0;
}
static u8 inject_lfi_check(struct http_request* req,
struct http_response* res) {
DEBUG_MISC_CALLBACK(req, res);
u8 found = 0;
u32 p = 0;
/*
Perform directory traveral and file inclusion tests.
In every request, the check_subid points to the relevant traversal
test string. We look up the string and compare it with the response.
Exception to this are the web.xml requests for which the injection
strings are dynamically generated. We don't know where the web.xml
is located on the file system (unlike /etc/passwd), we cannot use
an arbitrary amount of ../'s. Instead, we need to use the exact
amount in order to be able to disclose the file.
*/
for (p=0; p < req->pivot->pending; p++) {
if (MREQ(p)->check_subid <= MAX_LFI_INDEX) {
/* Test the parent and current response */
if (!inl_findstr(RPRES(req)->payload,
(u8*)lfi_tests[MREQ(p)->check_subid].test_string, 1024) &&
inl_findstr(MRES(p)->payload,
(u8*)lfi_tests[MREQ(p)->check_subid].test_string, 1024)) {
problem(PROB_FI_LOCAL, MREQ(p), MRES(p),
(u8*)lfi_tests[MREQ(p)->check_subid].description, req->pivot, 0);
found = 1;
}
} else if (MREQ(p)->check_subid == MAX_LFI_INDEX + 1) {
/* Check the web.xml disclosure */
if (!inl_findstr(RPRES(req)->payload, (u8*)"<web-app", 1024) &&
inl_findstr(MRES(p)->payload, (u8*)"<web-app", 1024)) {
if (inl_findstr(MRES(p)->payload, (u8*)"<servlet-mapping>", 2048) ||
inl_findstr(MRES(p)->payload, (u8*)"<security-constraint>", 2048) ||
inl_findstr(MRES(p)->payload, (u8*)"<welcome-file>", 2048)) {
problem(PROB_FI_LOCAL, MREQ(p), MRES(p),
(u8*)"response resembles web.xml.", req->pivot, 0);
found = 1;
}
}
}
}
/* If we disclosed something: suppress the more generic traversal
warnings by removing the issue. */
if (found)
remove_issue(req->pivot, PROB_DIR_TRAVERSAL);
return 0;
}
static u8 inject_rfi_tests(struct pivot_desc* pivot) {
struct http_request* n;
/* Perhaps do this in state manager ?*/
if (pivot->state == PSTATE_CHILD_INJECT)
return 1;
n = req_copy(pivot->req, pivot, 1);
ck_free(TPAR(n));
TPAR(n) = ck_strdup((u8*)RFI_HOST);
n->callback = inject_state_manager;
n->user_val = i;
async_request(n);
#endif
return 0;
}
static u8 inject_rfi_check(struct http_request* req,
struct http_response* res) {
static u8 inject_inclusion_check(struct http_request* req,
struct http_response* res) {
DEBUG_MISC_CALLBACK(req, res);
DEBUG_CALLBACK(req, res);
u32 not_found = 0;
/*
Perform directory traveral and file inclusion tests.
misc[1] = ../../../../../../../../etc/hosts
misc[2] = ../../../../../../../../etc/hosts\0
misc[3] = ../../../../../../../../etc/passwd
misc[4] = ../../../../../../../../etc/passwd\0
misc[5] = ..\..\..\..\..\..\..\..\boot.ini
misc[6] = ..\..\..\..\..\..\..\..\boot.ini\0
misc[7] = ../../../../../../../../WEB-INF/web.xml
misc[8] = ../../../../../../../../WEB-INF/web.xml\0
misc[9] = file:///etc/hosts
misc[10] = file:///etc/passwd
misc[11] = file:///boot.ini
misc[12] = RFI (optional)
*/
/* Check on the /etc/hosts file disclosure */
if (!inl_findstr(RPRES(req)->payload, (u8*)"127.0.0.1", 1024)) {
if (inl_findstr(MRES(0)->payload, (u8*)"127.0.0.1", 1024)) {
problem(PROB_FI_LOCAL, MREQ(0), MRES(0),
(u8*)"response resembles /etc/hosts (traversal)", req->pivot, 0);
} else if (inl_findstr(MRES(1)->payload, (u8*)"127.0.0.1", 1024)) {
problem(PROB_FI_LOCAL, MREQ(1), MRES(1),
(u8*)"response resembles /etc/hosts (traversal with NULL byte)", req->pivot, 0);
} else 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 file://)", req->pivot, 0);
} else not_found++;
}
/* Check on the /etc/passwd file disclosure */
if (!inl_findstr(RPRES(req)->payload, (u8*)"root:x:0:0:root", 1024)) {
if (inl_findstr(MRES(2)->payload, (u8*)"root:x:0:0:root", 1024)) {
problem(PROB_FI_LOCAL, MREQ(2), MRES(2),
(u8*)"response resembles /etc/passwd (via traversal)", req->pivot, 0);
} else if (inl_findstr(MRES(3)->payload, (u8*)"root:x:0:0:root", 1024)) {
problem(PROB_FI_LOCAL, MREQ(3), MRES(3),
(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);
} else not_found++;
}
/* Windows boot.ini disclosure */
if (!inl_findstr(RPRES(req)->payload, (u8*)"[boot loader]", 1024)) {
if (inl_findstr(MRES(4)->payload, (u8*)"[boot loader]", 1024)) {
problem(PROB_FI_LOCAL, MREQ(4), MRES(4),
(u8*)"response resembles c:\\boot.ini (via traversal)", req->pivot, 0);
} else if (inl_findstr(MRES(5)->payload, (u8*)"[boot loader]", 1024)) {
problem(PROB_FI_LOCAL, MREQ(5), MRES(5),
(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(10),
(u8*)"response resembles c:\\boot.ini (via file://)", req->pivot, 0);
} else not_found++;
}
/* Check the web.xml disclosure */
if (!inl_findstr(RPRES(req)->payload, (u8*)"<servlet-mapping>", 1024)) {
if (inl_findstr(MRES(6)->payload, (u8*)"<servlet-mapping>", 1024)) {
problem(PROB_FI_LOCAL, MREQ(6), MRES(10),
(u8*)"response resembles ./WEB-INF/web.xml (via traversal)", req->pivot, 0);
} else 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);
} else not_found++;
}
/* If we disclosed a file, than we can remove any present traversal
warnings, which in that case are just duplicate/noise */
if (not_found != 4)
remove_issue(req->pivot, PROB_DIR_TRAVERSAL);
#ifdef RFI_SUPPORT
if (!inl_findstr(RPRES(req)->payload, (u8*)RFI_STRING, 1024) &&
inl_findstr(MRES(11)->payload, (u8*)RFI_STRING, 1024)) {
problem(PROB_FI_REMOTE, MREQ(11), MRES(11),
inl_findstr(res->payload, (u8*)RFI_STRING, 1024)) {
problem(PROB_FI_REMOTE, req, res,
(u8*)"remote file inclusion", req->pivot, 0);
}
#endif
return 0;
}
static u8 inject_redir_tests(struct pivot_desc* pivot) {
struct http_request* n;
@ -1826,7 +1944,7 @@ static u8 param_behavior_tests(struct pivot_desc* pivot) {
if (pivot->fuzz_par < 0 || !url_allowed(pivot->req) || !param_allowed(pivot->name)) {
pivot->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pivot);
maybe_delete_payload(pivot);
return 0;
}
@ -1916,6 +2034,49 @@ static u8 param_behavior_check(struct http_request* req,
return 0;
}
/* Request this same URL with different user agents. Detect if the
response is different so that additional injection tests can be
scheduled. */
static u8 agent_behavior_tests(struct pivot_desc* pivot) {
struct http_request* n;
u32 i, j = 0;
/* Schedule browser specific requests. */
for (i=0; i<BROWSER_TYPE_CNT; i++) {
/* Skip the default browser */
if (browser_types[i] == pivot->browser)
continue;
n = req_copy(pivot->req, pivot, 1);
n->callback = inject_state_manager;
n->browser = browser_types[i];
n->user_val = j++;
async_request(n);
}
return 0;
}
static u8 agent_behavior_check(struct http_request* req,
struct http_response* res) {
u32 i;
DEBUG_MISC_CALLBACK(req, res);
for (i=0; i<BROWSER_TYPE_CNT - 1; i++) {
if (!same_page(&RPRES(req)->sig, &MRES(i)->sig)) {
req->pivot->browsers |= MREQ(i)->browser;
maybe_add_pivot(MREQ(i),NULL, 2);
}
}
return 0;
}
static u8 param_ognl_tests(struct pivot_desc* pivot) {

View File

@ -33,7 +33,7 @@ u8 inject_state_manager(struct http_request* req, struct http_response* res);
#define CHK_SESSION 5
#define CHK_DIR_LIST 6
#define CHK_PUT 7
#define CHK_FI 8
#define CHK_LFI 8
#define CHK_RFI 9
#define CHK_XSSI 10
#define CHK_PROLOG 11
@ -45,6 +45,7 @@ u8 inject_state_manager(struct http_request* req, struct http_response* res);
#define CHK_BEHAVE 17
#define CHK_IPS 18
#define CHK_RSPLIT 19
#define CHK_AGENT 20
#ifdef _VIA_CHECKS_C
@ -54,6 +55,17 @@ u8 inject_state_manager(struct http_request* req, struct http_response* res);
#define SLEEP_TEST_ONE 3
#define SLEEP_TEST_TWO 5
/* Browsers for behavior testing */
#define BROWSER_TYPE_CNT 4
u32 browser_types[BROWSER_TYPE_CNT] = {
BROWSER_FAST,
BROWSER_MSIE,
BROWSER_FFOX,
BROWSER_PHONE
};
/* Helper for calculating the request time */
#define RTIME(_r) (MREQ(_r)->end_time - MREQ(_r)->start_time)
@ -79,19 +91,27 @@ struct cb_handle {
/* Strings for traversal and file disclosure tests. The order should
not be changed */
static const char* disclosure_tests[] = {
"../../../../../../../../etc/hosts",
"..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fhosts%00.js",
"../../../../../../../../etc/passwd",
"..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd%00.js",
"..\\..\\..\\..\\..\\..\\..\\..\\boot.ini",
"..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5cboot.ini%00.js",
"../../../../../../../../WEB-INF/web.xml",
"..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fWEB-INF%2fweb.xml%3f.js",
"file:///etc/hosts",
"file:///etc/passwd",
"file:///boot.ini",
0
struct lfi_test {
const char *vectors[10];
const char *test_string;
const char *description;
};
#define MAX_LFI_INDEX 2
struct lfi_test lfi_tests[] = {
{{"/../../../../../../../../../etc/hosts",
"file:///etc/hosts", 0
}, "127.0.0.1", "File /etc/hosts was disclosed." },
{{"/../../../../../../../../../etc/passwd",
"file:///etc/passwd", 0
}, "root:x:0:0:root", "File /etc/passwd was disclosed."},
{{"..\\..\\..\\..\\..\\..\\..\\..\\boot.ini",
"file:///boot.ini", 0
}, "[boot loader]", "File boot.ini was disclosed."},
};
#endif /* _VIA_CHECKS_C */

View File

@ -27,12 +27,6 @@
#define SHOW_SPLASH 1 /* Annoy user with a splash screen */
/* Define this to enable experimental HTTP proxy support, through the -J
option in the command line. This mode will not work as expected for
HTTPS requests at this time - sorry. */
// #define PROXY_SUPPORT 1
/* Default paths to runtime files: */
#define ASSETS_DIR "assets"
@ -66,6 +60,12 @@
// #define QUEUE_FILO 1
/* Define this to enable experimental HTTP proxy support, through the -J
option in the command line. This mode will not work as expected for
HTTPS requests at this time. */
// #define PROXY_SUPPORT 1
/* Dummy file to upload to the server where possible. */
#define DUMMY_EXT "gif"
@ -157,16 +157,15 @@
#define XSRF_B64_NUM2 3 /* ...digit count override */
#define XSRF_B64_SLASH 2 /* ...maximum slash count */
#ifdef _VIA_CRAWLER_C
#ifdef _VIA_CHECKS_C
/* The URL and string we use in the RFI test */
/* The URL and string we use in the RFI test (disabled by default) */
#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 */
#endif /* _VIA_CHECKS_C */
#ifdef _VIA_DATABASE_C
@ -279,5 +278,4 @@ static const char* form_suggestion[][2] = {
};
#endif /* _VIA_ANALYSIS_C */
#endif /* ! _HAVE_CONFIG_H */

View File

@ -37,6 +37,7 @@ u32 crawl_prob = 100; /* Crawl probability (1-100%) */
u8 no_fuzz_ext; /* Don't fuzz extensions for dirs */
u8 no_500_dir; /* Don't crawl 500 directories */
u8 delete_bin; /* Don't keep binary responses */
u8 flush_pivot_data; /* Flush pivot data to disk */
/*
@ -574,7 +575,7 @@ static void param_start(struct pivot_desc* pv) {
if (pv->fuzz_par < 0 || !url_allowed(pv->req) || !param_allowed(pv->name)) {
pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pv);
maybe_delete_payload(pv);
return;
}
@ -606,7 +607,7 @@ void inject_done(struct pivot_desc* pv) {
} else {
pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pv);
maybe_delete_payload(pv);
return;
}
@ -615,7 +616,7 @@ void inject_done(struct pivot_desc* pv) {
if (pv->bogus_par || pv->res_varies) {
pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pv);
maybe_delete_payload(pv);
} else {
param_numerical_start(pv);
}
@ -755,14 +756,14 @@ static u8 param_numerical_check(struct http_request* req,
req->pivot = n;
RESP_CHECKS(req, res);
secondary_ext_start(orig_pv, req, res, 1);
if (delete_bin) maybe_delete_payload(n);
maybe_delete_payload(n);
schedule_next:
RESP_CHECKS(req, res);
if (!(--(orig_pv->num_pending))) {
orig_pv->state = PSTATE_PAR_DICT;
param_dict_start(orig_pv);
@ -947,15 +948,15 @@ static u8 param_dict_check(struct http_request* req,
keep = 1;
RESP_CHECKS(req, res);
if (!req->user_val)
secondary_ext_start(orig_pv, req, res, 1);
if (delete_bin) maybe_delete_payload(n);
maybe_delete_payload(n);
schedule_next:
RESP_CHECKS(req, res);
if (!req->user_val)
param_dict_start(orig_pv);
@ -974,7 +975,7 @@ void param_trylist_start(struct pivot_desc* pv) {
|| !descendants_ok(pv)) {
pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pv);
maybe_delete_payload(pv);
return;
} else
@ -1021,7 +1022,7 @@ void param_trylist_start(struct pivot_desc* pv) {
if (!pv->try_pending) {
pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(pv);
maybe_delete_payload(pv);
return;
}
@ -1082,17 +1083,17 @@ static u8 param_trylist_check(struct http_request* req,
req->pivot = n;
RESP_CHECKS(req, res);
secondary_ext_start(orig_pv, req, res, 1);
if (delete_bin) maybe_delete_payload(n);
maybe_delete_payload(n);
schedule_next:
RESP_CHECKS(req, res);
if (!(--(orig_pv->try_pending))) {
orig_pv->state = PSTATE_DONE;
if (delete_bin) maybe_delete_payload(orig_pv);
maybe_delete_payload(orig_pv);
}
/* Copied over to pivot. */

View File

@ -59,6 +59,13 @@ void authenticate();
} \
} while (0)
#define PREFIX_STRING(_orig, _str) do { \
u8* tmp = ck_alloc(strlen((char*)_orig) + strlen((char*)_str) + 1); \
sprintf((char*)tmp, "%s%s", (char*)_str, (char*)_orig); \
ck_free(_orig); \
_orig = tmp; \
} while (0)
/* Classifies a response, with a special handling of "unavailable" and
"gateway timeout" codes. */
@ -72,6 +79,7 @@ extern u8 no_parse, /* Disable HTML link detection */
no_fuzz_ext, /* Don't fuzz ext in dirs? */
no_500_dir, /* Don't assume dirs on 500 */
delete_bin, /* Don't keep binary responses */
flush_pivot_data, /* Flush pivot data to disk */
log_ext_urls; /* Log external URLs? */
/* Provisional debugging callback. */

View File

@ -138,7 +138,7 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
#ifdef LOG_STDERR
u8* url = serialize_path(req, 1, 1);
DEBUG("--- New pivot requested: %s (%d) --\n", url, via_link);
DEBUG("--- New pivot requested: %s (%d,%d)\n", url, via_link, req->browser);
ck_free(url);
#endif /* LOG_STDERR */
@ -261,7 +261,8 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
for (c=0;c<ccnt;c++)
if (!(is_c_sens(cur) ? strcmp : strcasecmp)((char*)pname,
(char*)cur->child[c]->name)) {
(char*)cur->child[c]->name) && (!req->browser ||
req->browser == cur->child[c]->browser)) {
cur = cur->child[c];
if (cur->linked < via_link) cur->linked = via_link;
break;
@ -307,6 +308,7 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
n->parent = cur;
n->linked = via_link;
n->name = ck_strdup(pname);
n->browser = req->browser;
/* Copy the original request, then copy over path up to the
current point. */
@ -456,7 +458,8 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
for (c=0;c<ccnt;c++)
if (!(is_c_sens(cur) ? strcmp : strcasecmp)((char*)pname,
(char*)cur->child[c]->name)) {
(char*)cur->child[c]->name) && (!req->browser ||
req->browser == cur->child[c]->browser)) {
cur = cur->child[c];
if (cur->linked < via_link) cur->linked = via_link;
break;
@ -490,6 +493,7 @@ void maybe_add_pivot(struct http_request* req, struct http_response* res,
n->type = PIVOT_PARAM;
n->linked = via_link;
n->name = ck_strdup(pname);
n->browser = req->browser;
/* Copy the original request, in full. Remember not to fuzz
file inputs. */
@ -1156,7 +1160,7 @@ void load_keywords(u8* fname, u8 read_only, u32 purge_age) {
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);
PFATAL("Unable to open read-write wordlist '%s' (see doc/dictionaries.txt).", fname);
}
sprintf(fmt, "%%2s %%u %%u %%u %%%u[^\x01-\x1f]", MAX_WORD);

View File

@ -88,6 +88,9 @@ struct pivot_desc {
struct http_request* req; /* Prototype HTTP request */
u8 browsers; /* Discovered user-agents */
u8 browser; /* The used user-agent */
s32 fuzz_par; /* Fuzz target parameter */
u8** try_list; /* Values to try */
u32 try_cnt; /* Number of values to try */
@ -139,7 +142,7 @@ struct pivot_desc {
/* Injection attack logic scratchpad: */
#define MISC_ENTRIES 15
#define MISC_ENTRIES 64
struct http_request* misc_req[MISC_ENTRIES]; /* Saved requests */
struct http_response* misc_res[MISC_ENTRIES]; /* Saved responses */
@ -290,6 +293,7 @@ void remove_issue(struct pivot_desc *pv, u32 type);
#define PROB_CACHE_LOW 30701 /* Cache nit-picking */
#define PROB_PROLOGUE 30801 /* User-supplied prologue */
#define PROB_XSS_VECTOR 30802 /* XSS vector, lower risk */
#define PROB_HEADER_INJECT 30901 /* Injected string in header */
@ -330,7 +334,7 @@ void remove_issue(struct pivot_desc *pv, u32 type);
#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_FI_REMOTE 50107 /* Remote file inclusion */
#define PROB_SQL_PARAM 50201 /* SQL-like parameter */
@ -472,14 +476,6 @@ void problem(u32 type, struct http_request* req, struct http_response* res,
u8 same_page(struct http_sig* sig1, struct http_sig* sig2);
/* URL filtering constraints (exported from database.c): */
#define APPEND_FILTER(_ptr, _cnt, _val) do { \
(_ptr) = ck_realloc(_ptr, ((_cnt) + 1) * sizeof(u8*)); \
(_ptr)[_cnt] = (u8*)(_val); \
(_cnt)++; \
} while (0)
extern u8 **deny_urls, **allow_urls, **allow_domains,
**trust_domains, **skip_params;

View File

@ -144,7 +144,8 @@ u8* get_value(u8 type, u8* name, u32 offset,
void set_value(u8 type, u8* name, u8* val,
s32 offset, struct param_array* par) {
u32 i, coff = 0, matched = -1;
u32 i, coff = 0;
s32 matched = -1;
/* If offset specified, try to find an entry to replace. */
@ -915,6 +916,7 @@ u8* build_request_data(struct http_request* req) {
u8 *ret_buf, *ck_buf, *pay_buf, *path;
u32 ret_pos, ck_pos, pay_pos, i;
u8 req_type = PARAM_NONE;
u8 browser = browser_type;
if (req->proto == PROTO_NONE)
FATAL("uninitialized http_request");
@ -962,9 +964,17 @@ u8* build_request_data(struct http_request* req) {
ASD("\r\n");
/* Insert generic browser headers first. */
/* Insert generic browser headers first. If the request is for a specific
browser, we use that. Else we use the pivot browser or, when that
is not set, fall back on the default browser (e.g. set with -b). */
if (browser_type == BROWSER_FAST) {
if (req->browser) {
browser = req->browser;
} else if (req->pivot->browser) {
browser = req->browser;
}
if (browser == BROWSER_FAST) {
ASD("Accept-Encoding: gzip\r\n");
ASD("Connection: keep-alive\r\n");
@ -975,7 +985,7 @@ u8* build_request_data(struct http_request* req) {
/* Some servers will reject to gzip responses unless "Mozilla/..."
is seen in User-Agent. Bleh. */
} else if (browser_type == BROWSER_FFOX) {
} else if (browser == BROWSER_FFOX) {
if (!GET_HDR((u8*)"User-Agent", &req->par))
ASD("User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; "
@ -991,7 +1001,7 @@ u8* build_request_data(struct http_request* req) {
ASD("Keep-Alive: 300\r\n");
ASD("Connection: keep-alive\r\n");
} else if (browser_type == BROWSER_MSIE) {
} else if (browser == BROWSER_MSIE) {
ASD("Accept: */*\r\n");
@ -1722,6 +1732,10 @@ void destroy_request(struct http_request* req) {
ck_free(req->method);
ck_free(req->host);
ck_free(req->orig_url);
if (req->flushed && req->flush_dir)
ck_free(req->flush_dir);
ck_free(req);
}
@ -1746,7 +1760,14 @@ void destroy_response(struct http_response* res) {
ck_free(res->header_mime);
ck_free(res->msg);
ck_free(res->payload);
/* Payload might have been flushed */
if (res->payload)
ck_free(res->payload);
if (res->flushed && res->flush_dir)
ck_free(res->flush_dir);
ck_free(res);
}
@ -1937,7 +1958,7 @@ static u8 match_cert_name(char* req_host, char* host) {
static void check_ssl(struct conn_entry* c) {
X509 *p;
SSL_CIPHER *cp;
const SSL_CIPHER *cp;
/* Test if a weak cipher has been negotiated */
cp = SSL_get_current_cipher(c->srv_ssl);
@ -2565,8 +2586,14 @@ struct http_response* res_copy(struct http_response* res) {
ret->pay_len = res->pay_len;
if (res->pay_len) {
ret->payload = ck_alloc(res->pay_len);
memcpy(ret->payload, res->payload, res->pay_len);
if (res->flushed && res->flush_dir) {
ret->flushed = 1;
ret->flush_dir = ck_strdup(res->flush_dir);
} else {
ret->payload = ck_alloc(res->pay_len);
memcpy(ret->payload, res->payload, res->pay_len);
}
}
memcpy(&ret->sig, &res->sig, sizeof(struct http_sig));

View File

@ -36,6 +36,7 @@ struct param_array {
u8* t; /* Type */
u8** n; /* Name */
u8** v; /* Value */
u8** h; /* Host */
u32 c; /* Count */
};
@ -126,14 +127,22 @@ struct http_request {
u8* trying_key; /* Current keyword ptr */
u8 trying_spec; /* Keyword specificity info */
u32 check_id; /* Injection test ID */
u32 check_subid; /* Injection test subid */
/* Used by injection tests: */
u8* fuzz_par_enc; /* Fuzz target encoding */
u8 no_cookies; /* Don't send cookies */
u8 browser; /* Use specified user-agent */
u32 start_time; /* Request start time */
u32 end_time; /* Request end time */
u8 flushed; /* Data is flushed to disk? */
u8* flush_dir; /* Location of data on disk */
};
/* Flags for http_response completion state: */
@ -194,6 +203,9 @@ struct http_response {
u8 stuff_checked; /* check_stuff() called? */
u8 scraped; /* scrape_response() called? */
u8 flushed; /* Data is flushed to disk? */
u8* flush_dir; /* Location of data on disk */
};
/* Open keep-alive connection descriptor: */
@ -430,7 +442,7 @@ extern u8 ignore_cookies,
#define BROWSER_FAST 0 /* Minimimal HTTP headers */
#define BROWSER_MSIE 1 /* Try to mimic MSIE */
#define BROWSER_FFOX 2 /* Try to mimic Firefox */
#define BROWSER_PHONE 3 /* Try to mimic iPhone */
#define BROWSER_PHONE 4 /* Try to mimic iPhone */
extern u8 browser_type;
@ -438,6 +450,7 @@ extern u8 browser_type;
#define AUTH_NONE 0 /* No authentication */
#define AUTH_BASIC 1 /* 'Basic' HTTP auth */
#define AUTH_FORM 2 /* Form HTTP auth */
extern u8 auth_type;

120
src/options.c Normal file
View File

@ -0,0 +1,120 @@
/*
skipfish - Config parsing
----------------------------------------
Author: Niels Heinen <heinenn@google.com>,
Copyright 2012 by Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <ctype.h>
#define _VIA_OPTIONS_C
#include "options.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "string-inl.h"
u8 **fargv;
u32 fargc = 0;
/* This function reads the configuration file turns them into
flags that are given to getopt_long. */
int read_config_file(const char *filename, int *_argc, char ***_argv) {
FILE *fh;
char line[MAX_LINE_LEN + 1];
char *val, *ptr;
u8 *tmp;
u32 idx, i;
APPEND_STRING(fargv, fargc, ck_strdup((u8*)*_argv[0]));
fh = fopen(filename, "r");
if (!fh) PFATAL("Unable to read config from: %s", filename);
while (!feof(fh) && fargc < MAX_ARGS && fgets(line, MAX_LINE_LEN, fh)) {
/* Skip comments and empty lines */
if (line[0] == '\n' || line[0] == '\r' || line[0] == '#')
continue;
/* NULL terminate the key */
idx = strcspn(line, " \t=");
if (idx == strlen(line))
FATAL("Config key error at line: %s", line);
line[idx] = '\0';
/* Find the beginning of the value. */
val = line + (idx + 1);
idx = strspn(val, " \t=");
if (idx == strlen(val))
FATAL("Config value error at line: %s", line);
val = val + idx;
/* Trim the unwanted characters from the value */
ptr = val + (strlen(val) - 1);
while(*ptr && *ptr < 0x21) {
*ptr = 0;
ptr--;
}
/* Done! Now we have a key/value pair. If the flag is set to 'false'
we will disregard this line. If the value is 'true', we will set
the flag without a value. In any other case, we will set the flag
and value */
if (val[0] == '\0')
FATAL("Empty value in config line: %s", line);
if (strcasecmp("false", val) == 0)
continue;
tmp = ck_alloc(strlen(line) + 3);
sprintf((char*)tmp, "--%s", line);
APPEND_STRING(fargv, fargc, tmp);
if (strncasecmp("true", val, 3) != 0)
APPEND_STRING(fargv, fargc, ck_strdup((u8*)val));
}
/* Copy arguments from command line into our array */
for (i=1; i<*_argc && fargc < MAX_ARGS; ++i)
APPEND_STRING(fargv, fargc, ck_strdup((u8*)(*_argv)[i]));
/* Replace original flags */
*_argc = fargc;
*_argv = (char **)fargv;
fclose(fh);
return 0;
}
/* Helper function to cleanup memory */
void destroy_config() {
if (fargc == 0) return;
while (fargc-- != 0)
ck_free(fargv[fargc]);
ck_free(fargv);
}

117
src/options.h Normal file
View File

@ -0,0 +1,117 @@
/*
skipfish - option and config parsing
------------------------------------
Author: Niels Heinen <heinenn@google.com>
Copyright 2012 by Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef __OPTIONS_H
#define __OPTIONS_H
#include <unistd.h>
#include <getopt.h>
/* Config file reader function */
int read_config_file(const char *filename, int *_argc, char ***_argv);
/* Config parsing cleanup function that releases memory */
void destroy_config();
/* Long flags */
#ifdef _VIA_OPTIONS_C
#define MAX_LINE_LEN 2048
#define MAX_ARGS 100
#else
/* The option string for getopt_long */
#define OPT_STRING "+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:uvw:x:z:"
struct option long_options[] = {
{"auth", required_argument, 0, 'A' },
{"host", required_argument, 0, 'F' },
{"cookie", required_argument, 0, 'C' },
{"reject-cookies", no_argument, 0, 'N' },
{"header", required_argument, 0, 'H' },
{"user-agent", required_argument, 0, 'b' },
#ifdef PROXY_SUPPORT
{"proxy", required_argument, 0, 'J' },
#endif /* PROXY_SUPPORT */
{"max-crawl-depth", required_argument, 0, 'd' },
{"max-crawl-child", required_argument, 0, 'c' },
{"max-crawl-descendants", required_argument, 0, 'x' },
{"max-request-total", required_argument, 0, 'r' },
{"max-request-rate", required_argument, 0, 'l'},
{"crawl-probability", required_argument, 0, 'p' },
{"seed", required_argument, 0, 'q' },
{"include-string", required_argument, 0, 'I' },
{"exclude-string", required_argument, 0, 'X' },
{"skip-parameter", required_argument, 0, 'K' },
{"no-form-submits", no_argument, 0, 'O' },
{"include-domain", required_argument, 0, 'D' },
{"no-html-parsing", no_argument, 0, 'P' },
{"no-extension-brute", no_argument, 0, 'Y' },
{"log-mixed-content", no_argument, 0, 'M' },
{"skip-error-pages", no_argument, 0, 'Z' },
{"log-external-urls", no_argument, 0, 'U' },
{"log-cache-mismatches", no_argument, 0, 'E' },
{"form-value", required_argument, 0, 'T' },
{"rw-wordlist", required_argument, 0, 'W' },
{"no-keyword-learning", no_argument, 0, 'L' },
{"wordlist", required_argument, 0, 'S'},
{"trust-domain", required_argument, 0, 'B' },
{"max-connections", required_argument, 0, 'g' },
{"max-host-connections", required_argument, 0, 'm' },
{"max-failed-requests", required_argument, 0, 'f' },
{"request-timeout", required_argument, 0, 't' },
{"network-timeout", required_argument, 0, 'w' },
{"idle-timeout", required_argument, 0, 'i' },
{"response-size", required_argument, 0, 's' },
{"discard-binary", no_argument, 0, 'e' },
{"output", required_argument, 0, 'o' },
{"help", no_argument, 0, 'h' },
{"quiet", no_argument, 0, 'u' },
{"verbose", no_argument, 0, 'v' },
{"scan-timeout", required_argument, 0, 'k'},
{"signatures", required_argument, 0, 'z'},
{"checks", no_argument, 0, 0},
{"checks-toggle", required_argument, 0, 0},
{"no-injection-tests", no_argument, 0, 0},
{"fast", no_argument, 0, 0},
{"flush-to-disk", no_argument, 0, 0},
{"config", required_argument, 0, 0},
{"auth-form", required_argument, 0, 0},
{"auth-form-target", required_argument, 0, 0},
{"auth-user", required_argument, 0, 0},
{"auth-user-field", required_argument, 0, 0},
{"auth-pass", required_argument, 0, 0},
{"auth-pass-field", required_argument, 0, 0},
{"auth-verify-url", required_argument, 0, 0},
{0, 0, 0, 0 }
};
#endif /* !__VIA_OPTIONS_C */
#endif /* __OPTIONS_H */

View File

@ -49,6 +49,7 @@ struct p_sig_desc {
static struct p_sig_desc* p_sig;
static u32 p_sig_cnt;
u8 suppress_dupes;
u8 *output_dir = NULL;
u32 verbosity = 0;
@ -414,44 +415,119 @@ static void describe_res(FILE* f, struct http_response* res) {
}
/* Helper to save request, response data. */
static void create_link(const char *dir, const char *target) {
if (!dir || !target) return;
u8 *tmp = ck_alloc(strlen(dir) + strlen(target) + 2);
sprintf((char*)tmp, "%s/%s", dir, target);
if (link((char*)tmp, target) == -1)
PFATAL("Unable to create hardlink: %s -> %s\n", dir, target);
ck_free(tmp);
}
/* Helper function to collects MIME samples. */
static void collect_samples(struct http_request *req, struct http_response *res) {
u32 i;
/* No data, no samples */
if (!req || !res) return;
if (!req->pivot->dupe && res->sniffed_mime) {
for (i=0;i<m_samp_cnt;i++)
if (!strcmp((char*)m_samp[i].det_mime, (char*)res->sniffed_mime)) break;
if (i == m_samp_cnt) {
m_samp = ck_realloc(m_samp, (i + 1) * sizeof(struct mime_sample_desc));
m_samp[i].det_mime = res->sniffed_mime;
m_samp_cnt++;
} else {
u32 c;
/* If we already have something that looks very much the same on the
list, don't bother reporting it again. */
for (c=0;c<m_samp[i].sample_cnt;c++)
if (same_page(&m_samp[i].res[c]->sig, &res->sig)) return;
}
m_samp[i].req = ck_realloc(m_samp[i].req, (m_samp[i].sample_cnt + 1) *
sizeof(struct http_request*));
m_samp[i].res = ck_realloc(m_samp[i].res, (m_samp[i].sample_cnt + 1) *
sizeof(struct http_response*));
m_samp[i].req[m_samp[i].sample_cnt] = req;
m_samp[i].res[m_samp[i].sample_cnt] = res;
m_samp[i].sample_cnt++;
}
}
static void save_req_res(struct http_request* req, struct http_response* res, u8 sample) {
FILE* f;
FILE *f;
u8 *rd, *rs;
u32 i;
if (req) {
u8* rd = build_request_data(req);
f = fopen("request.dat", "w");
if (!f) PFATAL("Cannot create 'request.dat'");
if (fwrite(rd, strlen((char*)rd), 1, f)) {};
fclose(f);
/* Write .js file with base64 encoded json data. */
u32 size = 0;
u8* rd_js;
NEW_STR(rd_js, size);
ADD_STR_DATA(rd_js, size, "var req = {'data':'");
ADD_STR_DATA(rd_js, size, js_escape(rd, 0));
ADD_STR_DATA(rd_js, size, "'}");
f = fopen("request.js", "w");
if (!f) PFATAL("Cannot create 'request.js'");
if (fwrite(rd_js, strlen((char*)rd_js), 1, f)) {};
fclose(f);
ck_free(rd_js);
ck_free(rd);
/* No request ? We should probably crash here but that's not nice when
writing down a large report. Will warn in debug mode. */
if (!req) {
DEBUG("ERROR: save_req_res() was called but req == NULL\n");
return;
}
if (res && req && res->state == STATE_OK) {
u32 i;
/* First check if the data has already been flushed to disk. If this
is the case, we'll make a copy/hardlink to the data in the CWD */
if (res && req->flushed && res->flushed) {
create_link((const char *)req->flush_dir, "request.dat");
create_link((const char *)req->flush_dir, "request.js");
create_link((const char *)res->flush_dir, "response.dat");
create_link((const char *)res->flush_dir, "response.js");
/* Collect samples */
if (sample) collect_samples(req, res);
/* And done! */
return;
}
/* Getting here means we need to write down the request and
response. This is typically required upon when we flush data from
pivots that are DONE or upon report writing. */
rd = build_request_data(req);
f = fopen("request.dat", "w");
if (!f) PFATAL("Cannot create 'request.dat'");
fprintf(f, "%s", (char*)rd);
fclose(f);
/* Write .js file with base64 encoded json data. */
f = fopen("request.js", "w");
if (!f) PFATAL("Cannot create 'request.js'");
fprintf(f, "var req = {'data':'%s'}", js_escape(rd, 0));
fclose(f);
ck_free(rd);
if (res && res->state == STATE_OK) {
f = fopen("response.dat", "w");
if (!f) PFATAL("Cannot create 'response.dat'");
u64 msg_size = strlen((char*)res->msg);
u64 rs_size = msg_size + strlen("HTTP/1.1 1000 \n") + 1;
u8* rs = ck_alloc(rs_size);
rs = ck_alloc(rs_size);
snprintf((char*)rs, rs_size -1, "HTTP/1.1 %u %s\n", res->code, res->msg);
u32 s = strlen((char*)rs);
@ -469,57 +545,98 @@ static void save_req_res(struct http_request* req, struct http_response* res, u8
ADD_STR_DATA(rs, s, res->payload);
}
if (fwrite(rs, strlen((char*)rs), 1, f)) {};
fprintf(f, "%s", rs);
fclose(f);
/* Write .js file with base64 encoded json data. */
u8* rs_js;
NEW_STR(rs_js, s);
ADD_STR_DATA(rs_js, s, "var res = {'data':'");
ADD_STR_DATA(rs_js, s, js_escape(rs, 0));
ADD_STR_DATA(rs_js, s, "'}");
f = fopen("response.js", "w");
if (!f) PFATAL("Cannot create 'response.js'");
if (fwrite(rs_js, strlen((char*)rs_js), 1, f)) {};
fprintf(f, "var res = {'data':'%s'}", js_escape(rs, 0));
fclose(f);
ck_free(rs_js);
ck_free(rs);
/* Also collect MIME samples at this point. */
if (!req->pivot->dupe && res->sniffed_mime && sample) {
for (i=0;i<m_samp_cnt;i++)
if (!strcmp((char*)m_samp[i].det_mime, (char*)res->sniffed_mime)) break;
if (i == m_samp_cnt) {
m_samp = ck_realloc(m_samp, (i + 1) * sizeof(struct mime_sample_desc));
m_samp[i].det_mime = res->sniffed_mime;
m_samp_cnt++;
} else {
u32 c;
/* If we already have something that looks very much the same on the
list, don't bother reporting it again. */
for (c=0;c<m_samp[i].sample_cnt;c++)
if (same_page(&m_samp[i].res[c]->sig, &res->sig)) return;
}
m_samp[i].req = ck_realloc(m_samp[i].req, (m_samp[i].sample_cnt + 1) *
sizeof(struct http_request*));
m_samp[i].res = ck_realloc(m_samp[i].res, (m_samp[i].sample_cnt + 1) *
sizeof(struct http_response*));
m_samp[i].req[m_samp[i].sample_cnt] = req;
m_samp[i].res[m_samp[i].sample_cnt] = res;
m_samp[i].sample_cnt++;
}
}
/* Collect samples if asked */
if (sample) collect_samples(req, res);
}
/* Flush payloads to tmp dir so we can free up memory */
u32 flush_cnt = 0;
u8 cache_created = 0;
void flush_payload(struct http_request* req, struct http_response* res) {
u8 *tmp;
char *cwd;
/* To simplify: we flush pairs */
if (!req || !res || res->state != STATE_OK)
return;
/* Only flush them once */
if (req->flushed || res->flushed)
return;
/* Create the flush directory when this function is called for the
first time */
if (!cache_created) {
char *flush_dir = (char*)ck_alloc(strlen((char*)output_dir) + 7);
sprintf(flush_dir, "%s/%s", (char*)output_dir, "cache");
if (mkdir(flush_dir, 0755))
PFATAL("Cannot create cache: %s", flush_dir);
ck_free(flush_dir);
cache_created = 1;
}
cwd = getcwd(NULL, 0);
if (!cwd) PFATAL("Unable to get CWD.");
if (output_dir[0] == '/') {
tmp = ck_alloc(strlen((char*)output_dir) + 33);
sprintf((char*)tmp, "%s/cache/%u", (char*)output_dir, flush_cnt);
} else {
tmp = ck_alloc(strlen(cwd) + strlen((char*)output_dir) + 33);
sprintf((char*)tmp, "%s/%s/cache/%u", cwd, (char*)output_dir, flush_cnt);
}
if (mkdir((char*)tmp, 0755) == -1)
PFATAL("Cannot create cache subdir: %s", tmp);
if (chdir((char*)tmp) == -1)
PFATAL("chdir to %s unexpectedly fails!", tmp);
/* Record the location */
req->flush_dir = ck_strdup(tmp);
res->flush_dir = ck_strdup(tmp);
ck_free(tmp);
save_req_res(req, res, 0);
req->flushed = 1;
res->flushed = 1;
/* The big memory saver */
if (res->payload) {
ck_free(res->payload);
res->payload = NULL;
}
if (chdir(cwd) == -1)
PFATAL("chdir to %s unexpectedly fails!", cwd);
free(cwd);
flush_cnt++;
}
@ -836,12 +953,24 @@ static void save_pivots(FILE* f, struct pivot_desc* cur) {
default: fprintf(f, "linked=yes ");
}
/* When in no_checks mode, we'll report all the detected files and
directories. We don't do this when crawling is disabled.. */
if(!cur->linked && no_checks && !no_parse)
/* Report items that were not linked in any way. Unless -P is used in which
case crawling is disabled and therefore this will cause many false
alerts. */
if (!cur->linked && !no_parse && !cur->missing)
problem(PROB_HIDDEN_NODE, cur->req, cur->res, 0, cur, 0);
/* Record pivots where different agents result in different responses */
fprintf(f, "browsers=%d ", cur->browsers ? cur->browsers : browser_type);
/* Print the user agent type used */
switch (cur->browser) {
case 0: fprintf(f, "browser_used=FAST "); break;
case 1: fprintf(f, "browser_used=MSIE "); break;
case 2: fprintf(f, "browser_used=FFOX "); break;
case 4: fprintf(f, "browser_used=PHONE "); break;
default: fprintf(f, "browser_used=??? ");
}
if (cur->res)
fprintf(f, "dup=%u %s%scode=%u len=%u notes=%u sig=0x%x\n", cur->dupe,
cur->bogus_par ? "bogus " : "",
@ -851,6 +980,8 @@ static void save_pivots(FILE* f, struct pivot_desc* cur) {
else
fprintf(f, "not_fetched\n");
}
for (i=0;i<cur->child_cnt;i++) save_pivots(f, cur->child[i]);

View File

@ -25,12 +25,17 @@
#include "types.h"
extern u8 suppress_dupes;
extern u8 *output_dir;
/* Writes report to index.html in the current directory. Will create
subdirectories, helper files, etc. */
void write_report(u8* out_dir, u64 scan_time, u32 seed);
/* Flushes payload to disk and stores the location in the pivot */
void flush_payload(struct http_request* req, struct http_response* res);
/* Destroys all signatures created for pivot and issue clustering purposes. */
void destroy_signatures(void);

View File

@ -122,6 +122,18 @@ static u8 compile_content(struct content_struct *content) {
}
/* Look up a signature. */
static struct signature* get_signature(u32 id) {
u32 i;
for (i=0; i<slist_cnt; i++) {
if (sig_list[i]->id == id)
return sig_list[i];
}
return NULL;
}
/* Parses the signature string that is given as the first parameter and returns
a signature struct */
@ -145,31 +157,41 @@ struct signature* parse_sig(u8* tline) {
name++;
/* Split on the value and, for now, return NULL when there is no
value. Later we should add value-less keywords, like 'nocase' */
val = (u8*)index((char*)name, ':');
value. We check for keyworks without value, like nocase. */
if(!val) {
val = name;
while (*val && val++) {
if (*val == ':') {
*val = 0;
val++;
break;
}
if (*val == ';') break;
}
if(!*val) {
ck_free(sig);
ck_free(tline);
return 0;
}
*val = 0;
/* Check if ! is present and set 'not' */
if (++val && *val == '!') {
if (*val == '!') {
no = 1;
val++;
}
/* Move to value and check if quoted */
if (val && (*val == '\'' || *val == '"')) {
if (*val && (*val == '\'' || *val == '"')) {
in_quot = *val;
val++;
}
/* Find the end of the value string */
line = val;
while (++line) {
while (*line && (in_quot || *line != ';') && line++) {
if(*line == '\\') {
line++;
continue;
@ -179,15 +201,11 @@ struct signature* parse_sig(u8* tline) {
if (in_quot && *line == in_quot) {
in_quot = 0;
*line = 0;
line++;
continue;
}
/* End of the value? */
if (!in_quot && *line == ';') {
*line = 0;
break;
}
}
*line = 0;
switch (lookup(name)) {
case SIG_ID:
@ -223,6 +241,14 @@ struct signature* parse_sig(u8* tline) {
case SIG_MEMO:
sig->memo = unescape_str(val);
break;
case SIG_PCRE_MATCH:
if (!lcontent) {
WARN("Found 'regex_match' before 'content', skipping..");
break;
}
lcontent->cap_match_str = unescape_str(val);
break;
case SIG_TYPE:
if (!lcontent) {
WARN("Found 'type' before 'content', skipping..");
@ -247,6 +273,19 @@ struct signature* parse_sig(u8* tline) {
}
lcontent->offset = atoi((char*)val);
break;
case SIG_REPORT:
/* TODO(heinenn): support "never" */
if (!strcmp((char*)val, "once")) {
sig->report = REPORT_ONCE;
} else {
sig->report = REPORT_ALWAYS;
}
break;
case SIG_DEPEND:
/* Chain to another signature */
sig->depend = get_signature(atoi((char*)val));
break;
case SIG_SEV:
sig->severity = atoi((char*)val);
break;
@ -265,12 +304,27 @@ struct signature* parse_sig(u8* tline) {
WARN("Found 'case' before 'content', skipping..");
break;
}
if (!strcmp((char*)val, "no"))
lcontent->nocase = 1;
lcontent->nocase = 1;
break;
case SIG_PROTO:
if (!strcmp((char*)val, "https")) {
sig->proto = PROTO_HTTPS;
} else if (!strcmp((char*)val, "http")) {
sig->proto = PROTO_HTTP;
} else {
WARN("Unknown proto specified: %s (skipping)", val);
}
break;
case SIG_MIME:
sig->mime = unescape_str(val);
break;
case SIG_HEADER:
if (sig->header) {
FATAL("Found multiple 'header' keywords, skipping..");
break;
}
sig->header = unescape_str(val);
break;
case SIG_CODE:
sig->rcode = atoi((char*)val);
break;
@ -314,7 +368,7 @@ struct signature* parse_sig(u8* tline) {
void load_signatures(u8* fname) {
FILE* in;
u8 tmp[MAX_SIG_LEN];
u8 tmp[MAX_SIG_LEN + 1];
u8 include[MAX_SIG_FNAME + 1];
u32 in_cnt = 0;
u8 fmt[20];
@ -331,10 +385,23 @@ void load_signatures(u8* fname) {
if (!sig_list)
sig_list = ck_alloc(sizeof(struct signature*) * MAX_SIG_CNT);
while (fgets((char*)tmp, MAX_SIG_LEN, in)) {
if (tmp[0] == '#')
u32 sig_off = 0;
s32 tmp_off = 0;
while (fgets((char*)tmp + sig_off, MAX_SIG_LEN - sig_off, in)) {
if (tmp[0] == '#' || tmp[0] == '\n' || tmp[0] == '\r')
continue;
/* We concat signature lines that end with a trailing \ */
tmp_off = strlen((char*)tmp) - 1;
while (tmp_off && isspace(tmp[tmp_off]))
tmp_off--;
if (tmp[tmp_off] == '\\') {
sig_off = tmp_off;
continue;
}
/* When the include directive is present, we'll follow it */
if (!strncmp((char*)tmp, "include ", 8)) {
@ -345,14 +412,15 @@ void load_signatures(u8* fname) {
FATAL("Too many signature includes (max: %d)\n", MAX_SIG_INCS);
sprintf((char*)fmt, "%%%u[^\x01-\x1f]", MAX_SIG_FNAME);
sscanf((char*)tmp + 8,(char*)fmt, (char*)&include);
sscanf((char*)tmp + 8,(char*)fmt, (char*)include);
DEBUG("- Including signature file: %s\n", include);
load_signatures(include);
continue;
}
sig = parse_sig(tmp);
sig_off = 0;
if(sig == NULL)
continue;
@ -368,17 +436,40 @@ void load_signatures(u8* fname) {
fclose(in);
}
/* Helper function to check if a certain signature matched. This is,
for example, useful to chain signatures. */
static u8 matched_sig(struct pivot_desc *pv, u32 sid) {
u32 i;
if (!pv->issue_cnt) return 0;
/* Will optimise this later by changing the way signature match
information is stored per pivot */
for (i=0; i<pv->issue_cnt; i++) {
if (pv->issue[i].sid == sid)
return 1;
}
return 0;
}
u8 match_signatures(struct http_request *req, struct http_response *res) {
u8 pcre_ret, matches = 0;
u8 *payload, *match = NULL;
u32 ovector[PCRE_VECTOR];
u32 pay_len, j = 0, i = 0;
struct pivot_desc *tpv;
u32 ccnt, pay_len, j = 0, i = 0;
struct content_struct *content = NULL;
for ( j = 0; j < slist_cnt; j++ ) {
/* Check the signature is protocol specific (e.g. SSL-only) */
if (sig_list[j]->proto && (sig_list[j]->proto != req->proto))
continue;
/* Check if the signature is only intended for one of the active tests. */
if (sig_list[j]->check && (req->pivot->check_id > 0 &&
req->pivot->check_id != sig_list[j]->check)) {
@ -388,6 +479,21 @@ u8 match_signatures(struct http_request *req, struct http_response *res) {
/* Compare response code */
if (sig_list[j]->rcode && sig_list[j]->rcode != res->code)
continue;
/* If dependent on another signature, first check if that signature
matches. If it than we're done with this sig */
if (sig_list[j]->depend) {
tpv = req->pivot;
/* If report == 1 then we need to look at the host pivot */
if (sig_list[j]->depend->report == REPORT_ONCE)
tpv = host_pivot(req->pivot);
/* Do the check */
if(!matched_sig(tpv, sig_list[j]->depend->id))
continue;
}
/* Compare the mime types */
if (sig_list[j]->mime && res->header_mime) {
/* Skip if the mime doesn't match */
@ -410,8 +516,25 @@ u8 match_signatures(struct http_request *req, struct http_response *res) {
if (res->doc_type == 1 || !sig_list[j]->content_cnt)
continue;
payload = res->payload;
pay_len = res->pay_len;
/* If this is a header signature, than this is our payload for
matching. Else, we'll take the response body */
if (!sig_list[j]->header) {
payload = res->payload;
pay_len = res->pay_len;
} else {
/* Header is the payload */
payload = GET_HDR(sig_list[j]->header, &res->hdr);
/* A header might very well not be present which means we can
continue with the next signature */
if (!payload) continue;
pay_len = strlen((char*)payload);
}
matches = 0;
for (i=0; pay_len > 0 && i<sig_list[j]->content_cnt; i++) {
@ -467,6 +590,35 @@ u8 match_signatures(struct http_request *req, struct http_response *res) {
/* We care about the first match and update the match pointer
to the first byte that follows the matching string */
/* Check if a string was captured */
pcre_fullinfo(content->pcre_sig, NULL, PCRE_INFO_CAPTURECOUNT, &ccnt);
if (ccnt > 0 && content->cap_match_str) {
/* In pcre we trust.. We only allow one string to be
captured so while we could loop over ccnt: we just grab
the first string. */
u32 cap_size = ovector[3] - ovector[2];
if (cap_size > MAX_PCRE_CSTR_SIZE)
cap_size = MAX_PCRE_CSTR_SIZE;
u8 *pcre_cap_str = ck_alloc(cap_size + 1);
if (pcre_copy_substring((char*)payload, (int*)ovector, 2, 1,
(char*)pcre_cap_str, cap_size)) {
/* No match? break the loop */
if (inl_strcasestr(pcre_cap_str, content->cap_match_str)) {
ck_free(pcre_cap_str);
break;
}
}
ck_free(pcre_cap_str);
}
/* Move to the first byte after the match */
payload = payload + ovector[1];
pay_len -= (ovector[1] - ovector[0]);
/* pay_len is checked in the next match */
@ -497,8 +649,9 @@ void signature_problem(struct signature *sig,
#else
/* Register the problem, together with the sid */
register_problem((sig->prob ? sig->prob : sig_serv[sig->severity]), sig->id,
req, res, (sig->memo ? sig->memo : (u8*)""), req->pivot, 0);
register_problem((sig->prob ? sig->prob : sig_serv[sig->severity]),
sig->id, req, res, (sig->memo ? sig->memo : (u8*)""),
sig->report ? host_pivot(req->pivot) : req->pivot, 0);
#endif
}
@ -508,6 +661,7 @@ void destroy_signature(struct signature *sig) {
if (sig->memo) ck_free(sig->memo);
if (sig->mime) ck_free(sig->mime);
if (sig->header) ck_free(sig->header);
for (i=0; i<sig->content_cnt; i++) {
ck_free(sig->content[i]->match_str);
@ -548,6 +702,7 @@ void dump_sig(struct signature *sig) {
DEBUG(" %d. offset = %d\n", i, sig->content[i]->offset);
DEBUG(" %d. depth = %d\n", i, sig->content[i]->depth);
DEBUG(" %d. position = %d\n", i, sig->content[i]->distance);
DEBUG(" %d. nocase = %d\n", i, sig->content[i]->nocase);
DEBUG(" %d. no = %d\n", i, sig->content[i]->no);
}
@ -558,4 +713,18 @@ void dump_sig(struct signature *sig) {
DEBUG(" mime = %s\n", sig->mime);
if (sig->rcode)
DEBUG(" code = %d\n", sig->rcode);
DEBUG(" depend = %d\n", sig->depend ? sig->depend->id : 0);
DEBUG(" header = %s\n", sig->header ? (char*)sig->header : (char*)"");
switch (sig->proto) {
case '0':
DEBUG(" proto = HTTP/HTTPS\n");
break;
case PROTO_HTTP:
DEBUG(" proto = HTTP\n");
break;
case PROTO_HTTPS:
DEBUG(" proto = HTTPS\n");
}
}

View File

@ -28,12 +28,14 @@
#define MAX_CONTENT 10
#define PCRE_VECTOR 30
#define MAX_PCRE_CSTR_SIZE 256
struct content_struct {
u8* match_str; /* The content string to find */
u32 match_str_len; /* Length of the content string */
pcre* pcre_sig; /* Regex: compiled */
pcre_extra* pcre_extra_sig; /* Regex: extra */
u8* match_str; /* The content string to find */
u32 match_str_len; /* Length of the content string */
pcre* pcre_sig; /* Regex: compiled */
pcre_extra* pcre_extra_sig; /* Regex: extra */
u8 *cap_match_str; /* To compare with pcre cap string */
u8 no; /* 1 = string should not be there */
u8 nocase; /* 1 = case insensitive matching */
@ -49,9 +51,13 @@ struct signature {
u8 severity; /* Severity */
u32 prob; /* Problem ID from analysis.h */
u8* mime; /* Match with this mime type */
u8* header; /* Match with this mime type */
u32 rcode; /* Match with HTTP resp code */
u32 content_cnt; /* Amount of contenrt structs */
u32 check; /* The check ID */
u8 report; /* 0 = always, 1 = once */
u8 proto; /* 0, PROTO_HTTP or PROTO_HTTPS */
struct signature *depend; /* Chain/depend on this sig */
struct content_struct* content[MAX_CONTENT];
};
@ -102,21 +108,26 @@ u32 sig_serv[] = {
void destroy_signature(struct signature *sig);
#define SIG_ID 1
#define SIG_CONTENT 2
#define SIG_MEMO 3
#define SIG_TYPE 4
#define SIG_SEV 5
#define SIG_CONST 6
#define SIG_PROB 7
#define SIG_TAG 8
#define SIG_MIME 9
#define SIG_CODE 10
#define SIG_CASE 11
#define SIG_DEPTH 12
#define SIG_OFFSET 13
#define SIG_DIST 14
#define SIG_CHK 15
#define SIG_ID 1
#define SIG_CONTENT 2
#define SIG_MEMO 3
#define SIG_TYPE 4
#define SIG_SEV 5
#define SIG_CONST 6
#define SIG_PROB 7
#define SIG_TAG 8
#define SIG_MIME 9
#define SIG_CODE 10
#define SIG_CASE 11
#define SIG_DEPTH 12
#define SIG_OFFSET 13
#define SIG_DIST 14
#define SIG_CHK 15
#define SIG_PROTO 16
#define SIG_HEADER 17
#define SIG_REPORT 18
#define SIG_DEPEND 29
#define SIG_PCRE_MATCH 30
/* The structs below are to for helping the signature parser */
@ -136,13 +147,25 @@ struct sig_key lookuptable[] = {
{ SIG_TAG, "tag" },
{ SIG_MIME, "mime" },
{ SIG_CODE, "code" },
{ SIG_CASE, "case" },
{ SIG_CASE, "nocase" },
{ SIG_DEPTH, "depth" },
{ SIG_OFFSET, "offset" },
{ SIG_PCRE_MATCH, "regex_match" },
{ SIG_DIST, "distance" },
{ SIG_CHK, "check" },
{ SIG_PROTO, "proto" },
{ SIG_HEADER, "header" },
{ SIG_REPORT, "report" },
{ SIG_DEPEND, "depend" },
{ 0, 0}
};
/* Specified whether and when a match should be reported */
#define REPORT_ALWAYS 0
#define REPORT_ONCE 1
#define REPORT_NEVER 2
#endif /* !_VIA_SIGNATURE_C */
#endif /* !_SIGNATURE_H */

View File

@ -44,6 +44,7 @@
#include "database.h"
#include "http_client.h"
#include "report.h"
#include "options.h"
#include "signatures.h"
#include "auth.h"
@ -138,10 +139,11 @@ 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"
"Other settings:\n\n"
" -l max_req - max requests per second (%f)\n"
" -k duration - stop scanning after the given duration h:m:s\n\n"
" -k duration - stop scanning after the given duration h:m:s\n"
" --config file - load the specified configuration file\n\n"
"Send comments and complaints to <heinenn@google.com>.\n", argv0,
max_depth, max_children, max_descendants, max_requests,
@ -193,7 +195,7 @@ void splash_screen(void) {
while (!stop_soon && fread(keybuf, 1, sizeof(keybuf), stdin) == 0 && time_cnt++ < 600)
usleep(100000);
}
#endif /* SHOW_SPLASH */
@ -246,7 +248,7 @@ static void read_urls(u8* fn) {
FATAL("Scan target '%s' in file '%s' is not a valid absolute URL.", url, fn);
if (!url_allowed_host(req))
APPEND_FILTER(allow_domains, num_allow_domains,
APPEND_STRING(allow_domains, num_allow_domains,
__DFL_ck_strdup(req->host));
if (!url_allowed(req))
@ -262,7 +264,6 @@ static void read_urls(u8* fn) {
fclose(f);
if (!loaded) FATAL("No valid URLs found in '%s'.", fn);
}
@ -271,14 +272,20 @@ static void read_urls(u8* fn) {
int main(int argc, char** argv) {
s32 opt;
u32 loop_cnt = 0, purge_age = 0, seed;
u8 sig_loaded = 0, show_once = 0, no_statistics = 0,
display_mode = 0, has_fake = 0;
u8 sig_loaded = 0, show_once = 0, no_statistics = 0, display_mode = 0;
s32 oindex = 0;
u8 *wordlist = NULL, *output_dir = NULL;
u8 *wordlist = NULL;
u8 *sig_list_strg = NULL;
u8 *gtimeout_str = NULL;
const char *config_file = NULL;
u32 gtimeout = 0;
#ifdef PROXY_SUPPORT
/* A bool to track whether a fake Host header is set which doesn't
work with the proxy support. */
u8 has_fake = 0;
#endif /* PROXY_SUPPORT */
struct termios term;
struct timeval tv;
u64 st_time, en_time;
@ -288,80 +295,37 @@ int main(int argc, char** argv) {
signal(SIGPIPE, SIG_IGN);
SSL_library_init();
/* Options, options, and options */
static struct option long_options[] = {
{"auth", required_argument, 0, 'A' },
{"host", required_argument, 0, 'F' },
{"cookie", required_argument, 0, 'C' },
{"reject-cookies", required_argument, 0, 'N' },
{"header", required_argument, 0, 'H' },
{"user-agent", required_argument, 0, 'b' },
#ifdef PROXY_SUPPORT
{"proxy", required_argument, 0, 'J' },
#endif /* PROXY_SUPPORT */
{"max-depth", required_argument, 0, 'd' },
{"max-child", required_argument, 0, 'c' },
{"max-descendants", required_argument, 0, 'x' },
{"max-requests", required_argument, 0, 'r' },
{"max-rate", required_argument, 0, 'l'},
{"probability", required_argument, 0, 'p' },
{"seed", required_argument, 0, 'q' },
{"include", required_argument, 0, 'I' },
{"exclude", required_argument, 0, 'X' },
{"skip-param", required_argument, 0, 'K' },
{"skip-forms", no_argument, 0, 'O' },
{"include-domain", required_argument, 0, 'D' },
{"ignore-links", no_argument, 0, 'P' },
{"no-ext-fuzzing", no_argument, 0, 'Y' },
{"log-mixed-content", no_argument, 0, 'M' },
{"skip-error-pages", no_argument, 0, 'Z' },
{"log-external-urls", no_argument, 0, 'U' },
{"log-cache-mismatches", no_argument, 0, 'E' },
{"form-value", no_argument, 0, 'T' },
{"rw-wordlist", required_argument, 0, 'W' },
{"no-keyword-learning", no_argument, 0, 'L' },
{"mode", required_argument, 0, 'J' },
{"wordlist", required_argument, 0, 'S'},
{"trust-domain", required_argument, 0, 'B' },
{"max-connections", required_argument, 0, 'g' },
{"max-host-connections", required_argument, 0, 'm' },
{"max-fail", required_argument, 0, 'f' },
{"request-timeout", required_argument, 0, 't' },
{"network-timeout", required_argument, 0, 'w' },
{"idle-timeout", required_argument, 0, 'i' },
{"response-size", required_argument, 0, 's' },
{"discard-binary", required_argument, 0, 'e' },
{"output", required_argument, 0, 'o' },
{"help", no_argument, 0, 'h' },
{"quiet", no_argument, 0, 'u' },
{"verbose", no_argument, 0, 'v' },
{"scan-timeout", required_argument, 0, 'k'},
{"signatures", required_argument, 0, 'z'},
{"checks", no_argument, 0, 0},
{"checks-toggle", required_argument, 0, 0},
{"no-checks", no_argument, 0, 0},
{"fast", no_argument, 0, 0},
{"auth-form", required_argument, 0, 0},
{"auth-form-target", required_argument, 0, 0},
{"auth-user", required_argument, 0, 0},
{"auth-user-field", required_argument, 0, 0},
{"auth-pass", required_argument, 0, 0},
{"auth-pass-field", required_argument, 0, 0},
{"auth-verify-url", required_argument, 0, 0},
{0, 0, 0, 0 }
};
/* Come up with a quasi-decent random seed. */
gettimeofday(&tv, NULL);
seed = tv.tv_usec ^ (tv.tv_sec << 16) ^ getpid();
SAY("skipfish version " VERSION " by <lcamtuf@google.com>\n");
SAY("skipfish web application scanner - version " VERSION "\n");
while ((opt = getopt_long(argc, argv,
"+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:uvw:x:z:",
/* We either parse command-line arguments or read them from a config
file. First we check if a config file was specified and read it
content into the argc and argv pointers */
while ((opt = getopt_long(argc, argv, OPT_STRING,
long_options, &oindex)) >= 0 && !config_file) {
if (!opt && !strcmp("config", long_options[oindex].name ))
config_file = optarg;
}
/* Reset the index */
oindex = 0;
optind = 1;
if (config_file) {
DEBUG("Reading configuration file: %s\n", config_file);
read_config_file(config_file, &argc, &argv);
}
/* Parse the command-line flags. If a configuration file was specified,
the options loaded from it are now present in argv and will therefore
be parsed here all together with the CMD options. */
while ((opt = getopt_long(argc, argv, OPT_STRING,
long_options, &oindex)) >= 0)
switch (opt) {
@ -397,7 +361,9 @@ int main(int argc, char** argv) {
if (fake_addr == (u32)-1)
FATAL("Could not parse IP address '%s'.", x + 1);
fake_host((u8*)optarg, fake_addr);
#ifdef PROXY_SUPPORT
has_fake = 1;
#endif /* PROXY_SUPPORT */
break;
}
@ -423,26 +389,26 @@ int main(int argc, char** argv) {
case 'D':
if (*optarg == '*') optarg++;
APPEND_FILTER(allow_domains, num_allow_domains, optarg);
APPEND_STRING(allow_domains, num_allow_domains, optarg);
break;
case 'K':
APPEND_FILTER(skip_params, num_skip_params, optarg);
APPEND_STRING(skip_params, num_skip_params, optarg);
break;
case 'B':
if (*optarg == '*') optarg++;
APPEND_FILTER(trust_domains, num_trust_domains, optarg);
APPEND_STRING(trust_domains, num_trust_domains, optarg);
break;
case 'I':
if (*optarg == '*') optarg++;
APPEND_FILTER(allow_urls, num_allow_urls, optarg);
APPEND_STRING(allow_urls, num_allow_urls, optarg);
break;
case 'X':
if (*optarg == '*') optarg++;
APPEND_FILTER(deny_urls, num_deny_urls, optarg);
APPEND_STRING(deny_urls, num_deny_urls, optarg);
break;
case 'T': {
@ -629,30 +595,34 @@ int main(int argc, char** argv) {
break;
case 0:
if (!strcmp("checks", long_options[oindex].name ))
if (!strcmp("checks", long_options[oindex].name )) {
display_injection_checks();
if (!strcmp("checks-toggle", long_options[oindex].name ))
} else if (!strcmp("checks-toggle", long_options[oindex].name )) {
toggle_injection_checks((u8*)optarg, 1, 1);
if (!strcmp("no-checks", long_options[oindex].name ))
} else if (!strcmp("no-injection-tests", long_options[oindex].name )) {
no_checks = 1;
if (!strcmp("signatures", long_options[oindex].name ))
} else if(!strcmp("flush-to-disk", long_options[oindex].name )) {
flush_pivot_data = 1;
} else if (!strcmp("signatures", long_options[oindex].name )) {
load_signatures((u8*)optarg);
if(!strcmp("fast", long_options[oindex].name ))
toggle_injection_checks((u8*)"2,4,5,13,14,15,16", 0, 0);
if (!strcmp("auth-form", long_options[oindex].name ))
} else if(!strcmp("fast", long_options[oindex].name )) {
toggle_injection_checks((u8*)"2,4,6,15,16,17", 0, 0);
} else if (!strcmp("auth-form", long_options[oindex].name )) {
auth_form = (u8*)optarg;
if (!strcmp("auth-user", long_options[oindex].name ))
auth_type = AUTH_FORM;
} else if (!strcmp("auth-user", long_options[oindex].name )) {
auth_user = (u8*)optarg;
if (!strcmp("auth-pass", long_options[oindex].name ))
} else if (!strcmp("auth-pass", long_options[oindex].name )) {
auth_pass = (u8*)optarg;
if (!strcmp("auth-pass-field", long_options[oindex].name ))
} else if (!strcmp("auth-pass-field", long_options[oindex].name )) {
auth_pass_field = (u8*)optarg;
if (!strcmp("auth-user-field", long_options[oindex].name ))
} else if (!strcmp("auth-user-field", long_options[oindex].name )) {
auth_user_field = (u8*)optarg;
if (!strcmp("auth-form-target", long_options[oindex].name ))
} else if (!strcmp("auth-form-target", long_options[oindex].name )) {
auth_form_target = (u8*)optarg;
if (!strcmp("auth-verify-url", long_options[oindex].name ))
} else if (!strcmp("auth-verify-url", long_options[oindex].name )) {
auth_verify_url = (u8*)optarg;
}
break;
@ -691,7 +661,7 @@ int main(int argc, char** argv) {
/* Parse the timeout string - format h:m:s */
if (gtimeout_str) {
int i = 0;
int m[3] = { 1, 60, 3600 };
int m[3] = { 3600, 60, 1 };
u8* tok = (u8*)strtok((char*)gtimeout_str, ":");
@ -723,7 +693,11 @@ int main(int argc, char** argv) {
if (sig_list_strg) load_signatures(sig_list_strg);
/* Try to authenticate when the auth_user and auth_pass fields are set. */
if (auth_user && auth_pass) {
if (auth_type == AUTH_FORM) {
if (!auth_user || !auth_pass)
FATAL("Authentication requires a username and password.");
/* Fire off the requests */
authenticate();
while (next_from_queue()) {
@ -740,8 +714,10 @@ int main(int argc, char** argv) {
FATAL("Authentication failed (use -uv for more info)\n");
break;
}
DEBUG("Authentication done!\n");
}
/* Schedule all URLs in the command line for scanning. */
while (optind < argc) {
@ -760,7 +736,7 @@ int main(int argc, char** argv) {
FATAL("Scan target '%s' is not a valid absolute URL.", argv[optind]);
if (!url_allowed_host(req))
APPEND_FILTER(allow_domains, num_allow_domains,
APPEND_STRING(allow_domains, num_allow_domains,
__DFL_ck_strdup(req->host));
if (!url_allowed(req))
@ -875,6 +851,7 @@ int main(int argc, char** argv) {
destroy_signature_lists();
destroy_http();
destroy_signatures();
destroy_config();
__TRK_report();
}
#endif /* DEBUG_ALLOCATOR */

View File

@ -61,6 +61,13 @@
strncasecmp((const char*)(_long), (const char*)(_short), \
strlen((const char*)(_short)))
/* Appends a string to a dynamic array by first extending it. */
#define APPEND_STRING(_ptr, _cnt, _val) do { \
(_ptr) = ck_realloc(_ptr, ((_cnt) + 1) * sizeof(u8*)); \
(_ptr)[_cnt] = (u8*)(_val); \
(_cnt)++; \
} while (0)
/* Modified NetBSD strcasestr() implementation (rolling strncasecmp). */