e29db14ace
- Improved visibility of the KnownIssues page (reports, Makefile). - The location of assets/ directory is now configurable. ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
780 lines
21 KiB
C
780 lines
21 KiB
C
/*
|
|
skipfish - post-processing and reporting
|
|
----------------------------------------
|
|
|
|
Author: Michal Zalewski <lcamtuf@google.com>
|
|
|
|
Copyright 2009, 2010 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 <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
#include <sys/fcntl.h>
|
|
|
|
#include "debug.h"
|
|
#include "config.h"
|
|
#include "types.h"
|
|
#include "http_client.h"
|
|
#include "database.h"
|
|
#include "crawler.h"
|
|
#include "analysis.h"
|
|
|
|
/* Pivot and issue signature data. */
|
|
|
|
struct p_sig_desc {
|
|
u8 type; /* Pivot type */
|
|
struct http_sig* res_sig; /* Response signature */
|
|
u32 issue_sig; /* Issues fingerprint */
|
|
u32 child_sig; /* Children fingerprint */
|
|
};
|
|
|
|
|
|
static struct p_sig_desc* p_sig;
|
|
static u32 p_sig_cnt;
|
|
u8 suppress_dupes;
|
|
|
|
|
|
/* Response, issue sample data. */
|
|
|
|
struct mime_sample_desc {
|
|
u8* det_mime;
|
|
struct http_request** req;
|
|
struct http_response** res;
|
|
u32 sample_cnt;
|
|
};
|
|
|
|
|
|
struct issue_sample_desc {
|
|
u32 type;
|
|
struct issue_desc** i;
|
|
u32 sample_cnt;
|
|
};
|
|
|
|
static struct mime_sample_desc* m_samp;
|
|
static struct issue_sample_desc* i_samp;
|
|
static u32 m_samp_cnt, i_samp_cnt;
|
|
|
|
|
|
/* qsort() helper for sort_annotate_pivot(). */
|
|
|
|
static int pivot_compar(const void* par1, const void* par2) {
|
|
const struct pivot_desc *p1 = *(struct pivot_desc**)par1,
|
|
*p2 = *(struct pivot_desc**)par2;
|
|
return strcasecmp((char*)p1->name, (char*)p2->name);
|
|
}
|
|
|
|
static int issue_compar(const void* par1, const void* par2) {
|
|
const struct issue_desc *i1 = par1, *i2 = par2;
|
|
return i2->type - i1->type;
|
|
}
|
|
|
|
|
|
/* Recursively annotates and sorts pivots. */
|
|
|
|
static void sort_annotate_pivot(struct pivot_desc* pv) {
|
|
u32 i, path_child = 0;
|
|
static u32 proc_cnt;
|
|
u8 *q1, *q2;
|
|
|
|
/* Add notes to all non-dir nodes with dir or file children... */
|
|
|
|
for (i=0;i<pv->child_cnt;i++) {
|
|
if (pv->child[i]->type == PIVOT_FILE || pv->child[i]->type == PIVOT_DIR) path_child = 1;
|
|
sort_annotate_pivot(pv->child[i]);
|
|
}
|
|
|
|
if (pv->type != PIVOT_DIR && pv->type != PIVOT_SERV &&
|
|
pv->type != PIVOT_ROOT && path_child)
|
|
problem(PROB_NOT_DIR, pv->req, pv->res, 0, pv, 0);
|
|
|
|
/* Non-parametric nodes with digits in the name were not brute-forced,
|
|
but the user might be interested in doing so. Skip images here. */
|
|
|
|
if (pv->fuzz_par == -1 && pv->res &&
|
|
(pv->res->sniff_mime_id < MIME_IMG_JPEG ||
|
|
pv->res->sniff_mime_id > MIME_AV_WMEDIA) &&
|
|
(pv->type == PIVOT_DIR || pv->type == PIVOT_FILE ||
|
|
pv->type == PIVOT_PATHINFO) && !pv->missing) {
|
|
i = strlen((char*)pv->name);
|
|
while (i--)
|
|
if (isdigit(pv->name[i])) {
|
|
problem(PROB_FUZZ_DIGIT, pv->req, pv->res, 0, pv, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Parametric nodes that seem to contain queries in parameters, and are not
|
|
marked as bogus_par, should be marked as dangerous. */
|
|
|
|
if (pv->fuzz_par != -1 && !pv->bogus_par &&
|
|
(((q1 = (u8*)strchr((char*)pv->req->par.v[pv->fuzz_par], '(')) &&
|
|
(q2 = (u8*)strchr((char*)pv->req->par.v[pv->fuzz_par], ')')) && q1 < q2)
|
|
||
|
|
((inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)"SELECT ") ||
|
|
inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)"DELETE ") ) &&
|
|
inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)" FROM ")) ||
|
|
(inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)"UPDATE ") ||
|
|
inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)" WHERE ")) ||
|
|
inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)"DROP TABLE ") ||
|
|
inl_strcasestr(pv->req->par.v[pv->fuzz_par], (u8*)" ORDER BY ")))
|
|
problem(PROB_SQL_PARAM, pv->req, pv->res, 0, pv, 0);
|
|
|
|
/* Sort children nodes and issues as appropriate. */
|
|
|
|
if (pv->child_cnt > 1)
|
|
qsort(pv->child, pv->child_cnt, sizeof(struct pivot_desc*), pivot_compar);
|
|
|
|
if (pv->issue_cnt > 1)
|
|
qsort(pv->issue, pv->issue_cnt, sizeof(struct issue_desc), issue_compar);
|
|
|
|
if ((!(proc_cnt++ % 50)) || pv->type == PIVOT_ROOT) {
|
|
SAY(cLGN "\r[+] " cNOR "Sorting and annotating crawl nodes: %u", proc_cnt);
|
|
fflush(0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Issue extra hashing helper. */
|
|
|
|
static inline u32 hash_extra(u8* str) {
|
|
register u32 ret = 0;
|
|
register u8 cur;
|
|
|
|
if (str)
|
|
while ((cur=*str)) {
|
|
ret = ~ret ^ (cur) ^
|
|
(cur << 5) ^ (~cur >> 5) ^
|
|
(cur << 10) ^ (~cur << 15) ^
|
|
(cur << 20) ^ (~cur << 25) ^
|
|
(cur << 30);
|
|
str++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/* Registers a new pivot signature, or updates an existing one. */
|
|
|
|
static void maybe_add_sig(struct pivot_desc* pv) {
|
|
u32 i, issue_sig = ~pv->issue_cnt,
|
|
child_sig = ~pv->child_cnt;
|
|
|
|
if (!pv->res) return;
|
|
|
|
/* Compute a rough children node signature based on children types. */
|
|
|
|
for (i=0;i<pv->child_cnt;i++)
|
|
child_sig ^= (hash_extra(pv->child[i]->name) ^
|
|
pv->child[i]->type) << (i % 16);
|
|
|
|
/* Do the same for all recorded issues. */
|
|
|
|
for (i=0;i<pv->issue_cnt;i++)
|
|
issue_sig ^= (hash_extra(pv->issue[i].extra) ^
|
|
pv->issue[i].type) << (i % 16);
|
|
|
|
/* Assign a simplified signature to the pivot. */
|
|
|
|
pv->pv_sig = (pv->type << 16) ^ ~child_sig ^ issue_sig;
|
|
|
|
/* See if a matching signature already exists. */
|
|
|
|
for (i=0;i<p_sig_cnt;i++)
|
|
if (p_sig[i].type == pv->type && p_sig[i].issue_sig == issue_sig &&
|
|
p_sig[i].child_sig == child_sig &&
|
|
same_page(p_sig[i].res_sig, &pv->res->sig)) {
|
|
|
|
pv->dupe = 1;
|
|
return;
|
|
|
|
}
|
|
|
|
/* No match - create a new one. */
|
|
|
|
p_sig = ck_realloc(p_sig, (p_sig_cnt + 1) * sizeof(struct p_sig_desc));
|
|
|
|
p_sig[p_sig_cnt].type = pv->type;
|
|
p_sig[p_sig_cnt].res_sig = &pv->res->sig;
|
|
p_sig[p_sig_cnt].issue_sig = issue_sig;
|
|
p_sig[p_sig_cnt].child_sig = child_sig;
|
|
p_sig_cnt++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Recursively collects unique signatures for pivots. */
|
|
|
|
static void collect_signatures(struct pivot_desc* pv) {
|
|
u32 i;
|
|
static u32 proc_cnt;
|
|
|
|
maybe_add_sig(pv);
|
|
for (i=0;i<pv->child_cnt;i++) collect_signatures(pv->child[i]);
|
|
|
|
if ((!(proc_cnt++ % 50)) || pv->type == PIVOT_ROOT) {
|
|
SAY(cLGN "\r[+] " cNOR "Looking for duplicate entries: %u", proc_cnt);
|
|
fflush(0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Destroys signature data (for memory profiling purposes). */
|
|
|
|
void destroy_signatures(void) {
|
|
u32 i;
|
|
|
|
ck_free(p_sig);
|
|
|
|
for (i=0;i<m_samp_cnt;i++) {
|
|
ck_free(m_samp[i].req);
|
|
ck_free(m_samp[i].res);
|
|
}
|
|
|
|
for (i=0;i<i_samp_cnt;i++)
|
|
ck_free(i_samp[i].i);
|
|
|
|
ck_free(m_samp);
|
|
ck_free(i_samp);
|
|
}
|
|
|
|
|
|
/* Prepares issue, pivot stats, backtracing through all children.
|
|
Do not count nodes that seem duplicate. */
|
|
|
|
static void compute_counts(struct pivot_desc* pv) {
|
|
u32 i;
|
|
struct pivot_desc* tmp = pv->parent;
|
|
static u32 proc_cnt;
|
|
|
|
for (i=0;i<pv->child_cnt;i++) compute_counts(pv->child[i]);
|
|
|
|
if (pv->dupe) return;
|
|
|
|
while (tmp) {
|
|
tmp->total_child_cnt++;
|
|
tmp = tmp->parent;
|
|
}
|
|
|
|
for (i=0;i<pv->issue_cnt;i++) {
|
|
u8 sev = PSEV(pv->issue[i].type);
|
|
tmp = pv;
|
|
while (tmp) {
|
|
tmp->total_issues[sev]++;
|
|
tmp = tmp->parent;
|
|
}
|
|
}
|
|
|
|
if ((!(proc_cnt++ % 50)) || pv->type == PIVOT_ROOT) {
|
|
SAY(cLGN "\r[+] " cNOR "Counting unique issues: %u", proc_cnt);
|
|
fflush(0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Helper to JS-escape data. Static buffer, will be destroyed on
|
|
subsequent calls. */
|
|
|
|
static inline u8* js_escape(u8* str) {
|
|
u32 len;
|
|
static u8* ret;
|
|
u8* opos;
|
|
|
|
if (!str) return (u8*)"[none]";
|
|
|
|
len = strlen((char*)str);
|
|
|
|
if (ret) free(ret);
|
|
opos = ret = __DFL_ck_alloc(len * 4 + 1);
|
|
|
|
while (len--) {
|
|
if (*str > 0x1f && *str < 0x80 && !strchr("<>\\'\"", *str)) {
|
|
*(opos++) = *(str++);
|
|
} else {
|
|
sprintf((char*)opos, "\\x%02x", *(str++));
|
|
opos += 4;
|
|
}
|
|
}
|
|
|
|
*opos = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
static void output_scan_info(u64 scan_time, u32 seed) {
|
|
FILE* f;
|
|
time_t t = time(NULL);
|
|
u8* ct = (u8*)ctime(&t);
|
|
|
|
if (isspace(ct[strlen((char*)ct)-1]))
|
|
ct[strlen((char*)ct)-1] = 0;
|
|
|
|
f = fopen("summary.js", "w");
|
|
if (!f) PFATAL("Cannot open 'summary.js'");
|
|
|
|
fprintf(f, "var sf_version = '%s';\n", VERSION);
|
|
fprintf(f, "var scan_date = '%s';\n", js_escape(ct));
|
|
fprintf(f, "var scan_seed = '0x%08x';\n", seed);
|
|
fprintf(f, "var scan_ms = %llu;\n", (long long)scan_time);
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
/* Helper to save request, response data. */
|
|
|
|
static void describe_res(FILE* f, struct http_response* res) {
|
|
|
|
if (!res) {
|
|
fprintf(f, "'fetched': false, 'error': 'Content not fetched'");
|
|
return;
|
|
}
|
|
|
|
switch (res->state) {
|
|
|
|
case 0 ... STATE_OK - 1:
|
|
fprintf(f, "'fetched': false, 'error': '(Reported while fetch in progress)'");
|
|
break;
|
|
|
|
case STATE_OK:
|
|
fprintf(f, "'fetched': true, 'code': %u, 'len': %u, 'decl_mime': '%s', ",
|
|
res->code, res->pay_len,
|
|
js_escape(res->header_mime));
|
|
|
|
fprintf(f, "'sniff_mime': '%s', 'cset': '%s'",
|
|
res->sniffed_mime ? res->sniffed_mime : (u8*)"[none]",
|
|
js_escape(res->header_charset ? res->header_charset
|
|
: res->meta_charset));
|
|
break;
|
|
|
|
case STATE_DNSERR:
|
|
fprintf(f, "'fetched': false, 'error': 'DNS error'");
|
|
break;
|
|
|
|
case STATE_LOCALERR:
|
|
fprintf(f, "'fetched': false, 'error': 'Local network error'");
|
|
break;
|
|
|
|
case STATE_CONNERR:
|
|
fprintf(f, "'fetched': false, 'error': 'Connection error'");
|
|
break;
|
|
|
|
case STATE_RESPERR:
|
|
fprintf(f, "'fetched': false, 'error': 'Malformed HTTP response'");
|
|
break;
|
|
|
|
case STATE_SUPPRESS:
|
|
fprintf(f, "'fetched': false, 'error': 'Limits exceeded'");
|
|
break;
|
|
|
|
|
|
default:
|
|
fprintf(f, "'fetched': false, 'error': 'Unknown error'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Helper to save request, response data. */
|
|
|
|
static void save_req_res(struct http_request* req, struct http_response* res, u8 sample) {
|
|
FILE* f;
|
|
|
|
if (req) {
|
|
u8* rd = build_request_data(req);
|
|
f = fopen("request.dat", "w");
|
|
if (!f) PFATAL("Cannot create 'request.dat'");
|
|
fwrite(rd, strlen((char*)rd), 1, f);
|
|
fclose(f);
|
|
ck_free(rd);
|
|
}
|
|
|
|
if (res && res->state == STATE_OK) {
|
|
u32 i;
|
|
f = fopen("response.dat", "w");
|
|
if (!f) PFATAL("Cannot create 'response.dat'");
|
|
fprintf(f, "HTTP/1.1 %u %s\n", res->code, res->msg);
|
|
|
|
for (i=0;i<res->hdr.c;i++)
|
|
if (res->hdr.t[i] == PARAM_HEADER)
|
|
fprintf(f, "%s: %s\n", res->hdr.n[i], res->hdr.v[i]);
|
|
else
|
|
fprintf(f, "Set-Cookie: %s=%s\n", res->hdr.n[i], res->hdr.v[i]);
|
|
|
|
fprintf(f, "\n");
|
|
fwrite(res->payload, res->pay_len, 1, f);
|
|
fclose(f);
|
|
|
|
/* 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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Dumps the actual crawl data. */
|
|
|
|
static void output_crawl_tree(struct pivot_desc* pv) {
|
|
u32 i;
|
|
FILE* f;
|
|
static u32 proc_cnt;
|
|
|
|
/* Save request, response. */
|
|
|
|
save_req_res(pv->req, pv->res, 1);
|
|
|
|
/* Write children information. Don't crawl children just yet,
|
|
because we could run out of file descriptors on a particularly
|
|
deep tree if we keep one open and recurse. */
|
|
|
|
f = fopen("child_index.js", "w");
|
|
if (!f) PFATAL("Cannot create 'child_index.js'.");
|
|
|
|
fprintf(f, "var child = [\n");
|
|
|
|
for (i=0;i<pv->child_cnt;i++) {
|
|
u8 tmp[32];
|
|
u8* p;
|
|
|
|
if (suppress_dupes && pv->child[i]->dupe &&
|
|
!pv->child[i]->total_child_cnt) continue;
|
|
|
|
/* Also completely suppress nodes that seem identical to the
|
|
previous one, and have a common prefix (as this implies
|
|
a mod_rewrite or htaccess filter). */
|
|
|
|
if (i && pv->child[i-1]->pv_sig == pv->child[i]->pv_sig) {
|
|
u8 *pn = pv->child[i-1]->name, *cn = pv->child[i]->name;
|
|
u32 pnd = strcspn((char*)pn, ".");
|
|
if (!strncasecmp((char*)pn, (char*)cn, pnd)) continue;
|
|
}
|
|
|
|
sprintf((char*)tmp, "c%u", i);
|
|
|
|
fprintf(f, " { 'dupe': %s, 'type': %u, 'name': '%s%s",
|
|
pv->child[i]->dupe ? "true" : "false",
|
|
pv->child[i]->type, js_escape(pv->child[i]->name),
|
|
(pv->child[i]->fuzz_par == -1 || pv->child[i]->type == PIVOT_VALUE)
|
|
? (u8*)"" : (u8*)"=");
|
|
|
|
fprintf(f, "%s', 'dir': '%s', 'linked': %d, ",
|
|
(pv->child[i]->fuzz_par == -1 || pv->child[i]->type == PIVOT_VALUE)
|
|
? (u8*)"" :
|
|
js_escape(pv->child[i]->req->par.v[pv->child[i]->fuzz_par]),
|
|
tmp, pv->child[i]->linked);
|
|
|
|
p = serialize_path(pv->child[i]->req, 1, 1);
|
|
fprintf(f, "'url': '%s', ", js_escape(p));
|
|
ck_free(p);
|
|
|
|
describe_res(f, pv->child[i]->res);
|
|
|
|
fprintf(f,", 'missing': %s, 'csens': %s, 'child_cnt': %u, "
|
|
"'issue_cnt': [ %u, %u, %u, %u, %u ] }%s\n",
|
|
pv->child[i]->missing ? "true" : "false",
|
|
pv->child[i]->csens ? "true" : "false",
|
|
pv->child[i]->total_child_cnt, pv->child[i]->total_issues[1],
|
|
pv->child[i]->total_issues[2], pv->child[i]->total_issues[3],
|
|
pv->child[i]->total_issues[4], pv->child[i]->total_issues[5],
|
|
(i == pv->child_cnt - 1) ? "" : ",");
|
|
}
|
|
|
|
fprintf(f, "];\n");
|
|
fclose(f);
|
|
|
|
/* Write issue index, issue dumps. */
|
|
|
|
f = fopen("issue_index.js", "w");
|
|
if (!f) PFATAL("Cannot create 'issue_index.js'.");
|
|
|
|
fprintf(f, "var issue = [\n");
|
|
|
|
for (i=0;i<pv->issue_cnt;i++) {
|
|
u8 tmp[32];
|
|
sprintf((char*)tmp, "i%u", i);
|
|
|
|
fprintf(f, " { 'severity': %u, 'type': %u, 'extra': '%s', ",
|
|
PSEV(pv->issue[i].type) - 1, pv->issue[i].type,
|
|
pv->issue[i].extra ? js_escape(pv->issue[i].extra) : (u8*)"");
|
|
|
|
describe_res(f, pv->issue[i].res);
|
|
|
|
fprintf(f, ", 'dir': '%s' }%s\n",
|
|
tmp, (i == pv->issue_cnt - 1) ? "" : ",");
|
|
|
|
if (mkdir((char*)tmp, 0755)) PFATAL("Cannot create '%s'.", tmp);
|
|
chdir((char*)tmp);
|
|
save_req_res(pv->issue[i].req, pv->issue[i].res, 1);
|
|
chdir((char*)"..");
|
|
|
|
/* Issue samples next! */
|
|
|
|
if (!pv->dupe) {
|
|
u32 c;
|
|
for (c=0;c<i_samp_cnt;c++)
|
|
if (i_samp[c].type == pv->issue[i].type) break;
|
|
|
|
if (c == i_samp_cnt) {
|
|
i_samp = ck_realloc(i_samp, (c + 1) * sizeof(struct issue_sample_desc));
|
|
i_samp_cnt++;
|
|
i_samp[c].type = pv->issue[i].type;
|
|
}
|
|
|
|
i_samp[c].i = ck_realloc(i_samp[c].i, (i_samp[c].sample_cnt + 1) *
|
|
sizeof(struct issue_desc*));
|
|
i_samp[c].i[i_samp[c].sample_cnt] = &pv->issue[i];
|
|
i_samp[c].sample_cnt++;
|
|
}
|
|
|
|
}
|
|
|
|
fprintf(f, "];\n");
|
|
fclose(f);
|
|
|
|
/* Actually crawl children. */
|
|
|
|
for (i=0;i<pv->child_cnt;i++) {
|
|
u8 tmp[32];
|
|
sprintf((char*)tmp, "c%u", i);
|
|
if (mkdir((char*)tmp, 0755)) PFATAL("Cannot create '%s'.", tmp);
|
|
chdir((char*)tmp);
|
|
output_crawl_tree(pv->child[i]);
|
|
chdir((char*)"..");
|
|
}
|
|
|
|
if ((!(proc_cnt++ % 50)) || pv->type == PIVOT_ROOT) {
|
|
SAY(cLGN "\r[+] " cNOR "Counting unique issues: %u", proc_cnt);
|
|
fflush(0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Writes previews of MIME types, issues. */
|
|
|
|
static int m_samp_qsort(const void* ptr1, const void* ptr2) {
|
|
const struct mime_sample_desc *p1 = ptr1, *p2 = ptr2;
|
|
return strcasecmp((char*)p1->det_mime, (char*)p2->det_mime);
|
|
}
|
|
|
|
static int i_samp_qsort(const void* ptr1, const void* ptr2) {
|
|
const struct issue_sample_desc *p1 = ptr1, *p2 = ptr2;
|
|
return p2->type - p1->type;
|
|
}
|
|
|
|
|
|
static void output_summary_views() {
|
|
u32 i;
|
|
FILE* f;
|
|
|
|
f = fopen("samples.js", "w");
|
|
if (!f) PFATAL("Cannot create 'samples.js'.");
|
|
|
|
qsort(m_samp, m_samp_cnt, sizeof(struct mime_sample_desc), m_samp_qsort);
|
|
qsort(i_samp, i_samp_cnt, sizeof(struct issue_sample_desc), i_samp_qsort);
|
|
|
|
fprintf(f, "var mime_samples = [\n");
|
|
|
|
for (i=0;i<m_samp_cnt;i++) {
|
|
u32 c;
|
|
u8 tmp[32];
|
|
u32 use_samp = (m_samp[i].sample_cnt > MAX_SAMPLES ? MAX_SAMPLES :
|
|
m_samp[i].sample_cnt);
|
|
|
|
sprintf((char*)tmp, "_m%u", i);
|
|
if (mkdir((char*)tmp, 0755)) PFATAL("Cannot create '%s'.", tmp);
|
|
chdir((char*)tmp);
|
|
|
|
fprintf(f, " { 'mime': '%s', 'samples': [\n", m_samp[i].det_mime);
|
|
|
|
for (c=0;c<use_samp;c++) {
|
|
u8 tmp2[32];
|
|
u8* p = serialize_path(m_samp[i].req[c], 1, 0);
|
|
sprintf((char*)tmp2, "%u", c);
|
|
if (mkdir((char*)tmp2, 0755)) PFATAL("Cannot create '%s'.", tmp2);
|
|
chdir((char*)tmp2);
|
|
save_req_res(m_samp[i].req[c], m_samp[i].res[c], 0);
|
|
chdir("..");
|
|
fprintf(f, " { 'url': '%s', 'dir': '%s/%s', 'linked': %d, 'len': %d"
|
|
" }%s\n", js_escape(p), tmp, tmp2,
|
|
m_samp[i].req[c]->pivot->linked, m_samp[i].res[c]->pay_len,
|
|
(c == use_samp - 1) ? " ]" : ",");
|
|
ck_free(p);
|
|
}
|
|
|
|
fprintf(f, " }%s\n", (i == m_samp_cnt - 1) ? "" : ",");
|
|
chdir("..");
|
|
}
|
|
|
|
fprintf(f, "];\n\n");
|
|
|
|
fprintf(f, "var issue_samples = [\n");
|
|
|
|
for (i=0;i<i_samp_cnt;i++) {
|
|
u32 c;
|
|
u8 tmp[32];
|
|
u32 use_samp = (i_samp[i].sample_cnt > MAX_SAMPLES ? MAX_SAMPLES :
|
|
i_samp[i].sample_cnt);
|
|
|
|
sprintf((char*)tmp, "_i%u", i);
|
|
if (mkdir((char*)tmp, 0755)) PFATAL("Cannot create '%s'.", tmp);
|
|
chdir((char*)tmp);
|
|
|
|
fprintf(f, " { 'severity': %d, 'type': %d, 'samples': [\n",
|
|
PSEV(i_samp[i].type) - 1, i_samp[i].type);
|
|
|
|
for (c=0;c<use_samp;c++) {
|
|
u8 tmp2[32];
|
|
u8* p = serialize_path(i_samp[i].i[c]->req, 1, 0);
|
|
sprintf((char*)tmp2, "%u", c);
|
|
if (mkdir((char*)tmp2, 0755)) PFATAL("Cannot create '%s'.", tmp2);
|
|
chdir((char*)tmp2);
|
|
save_req_res(i_samp[i].i[c]->req, i_samp[i].i[c]->res, 0);
|
|
chdir("..");
|
|
fprintf(f, " { 'url': '%s', ", js_escape(p));
|
|
fprintf(f, "'extra': '%s', 'dir': '%s/%s' }%s\n",
|
|
i_samp[i].i[c]->extra ? js_escape(i_samp[i].i[c]->extra) :
|
|
(u8*)"", tmp, tmp2,
|
|
(c == use_samp - 1) ? " ]" : ",");
|
|
ck_free(p);
|
|
}
|
|
|
|
fprintf(f, " }%s\n", (i == i_samp_cnt - 1) ? "" : ",");
|
|
chdir("..");
|
|
}
|
|
|
|
fprintf(f, "];\n\n");
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
/* Copies over assets to target directory. */
|
|
|
|
static u8* ca_out_dir;
|
|
|
|
static int copy_asset(const struct dirent* d) {
|
|
u8 *itmp, *otmp, buf[1024];
|
|
s32 i, o;
|
|
|
|
if (d->d_name[0] == '.' || !strcmp(d->d_name, "COPYING")) return 0;
|
|
|
|
itmp = ck_alloc(strlen(ASSETS_DIR) + strlen(d->d_name) + 2);
|
|
sprintf((char*)itmp, "%s/%s", ASSETS_DIR, d->d_name);
|
|
i = open((char*)itmp, O_RDONLY);
|
|
|
|
otmp = ck_alloc(strlen((char*)ca_out_dir) + strlen(d->d_name) + 2);
|
|
sprintf((char*)otmp, "%s/%s", ca_out_dir, d->d_name);
|
|
o = open((char*)otmp, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
|
|
|
if (i >= 0 && o >= 0) {
|
|
s32 c;
|
|
while ((c = read(i, buf, 1024)) > 0) write(o, buf, c);
|
|
}
|
|
|
|
close(i);
|
|
close(o);
|
|
|
|
ck_free(itmp);
|
|
ck_free(otmp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
static void copy_static_code(u8* out_dir) {
|
|
struct dirent** d;
|
|
ca_out_dir = out_dir;
|
|
scandir(ASSETS_DIR, &d, copy_asset, NULL);
|
|
}
|
|
|
|
|
|
/* 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) {
|
|
|
|
SAY(cLGN "[+] " cNOR "Copying static resources...\n");
|
|
copy_static_code(out_dir);
|
|
|
|
if (chdir((char*)out_dir)) PFATAL("Cannot chdir to '%s'", out_dir);
|
|
|
|
sort_annotate_pivot(&root_pivot);
|
|
SAY("\n");
|
|
|
|
collect_signatures(&root_pivot);
|
|
SAY("\n");
|
|
|
|
compute_counts(&root_pivot);
|
|
SAY("\n");
|
|
|
|
SAY(cLGN "[+] " cNOR "Writing scan description...\n");
|
|
output_scan_info(scan_time, seed);
|
|
|
|
output_crawl_tree(&root_pivot);
|
|
SAY("\n");
|
|
|
|
SAY(cLGN "[+] " cNOR "Generating summary views...\n");
|
|
output_summary_views();
|
|
|
|
SAY(cLGN "[+] " cNOR "Report saved to '" cLBL "%s/index.html" cNOR "' ["
|
|
cLBL "0x%08x" cNOR "].\n", out_dir, seed);
|
|
|
|
}
|