📄 ratproxy.c
字号:
/* <tag><script>... */ if (htmlstate == 0 && !strncasecmp(cur+2,"<qg",3)) return 1; /* <tag>+ADw-script+AD4-... */ if (htmlstate == 0 && (!not->charset || not->bad_cset) && !strncasecmp(cur+2,"+qg",3)) return 1; /* <tag foo="bar"onload=...> */ if (htmlstate == (HS_IN_TAG|HS_IN_DBLQ) && !strncasecmp(cur+2,"\"qg",3)) return 1; /* <tag foo='bar'onload=...> */ if (htmlstate == (HS_IN_TAG|HS_IN_SNGQ) && !strncasecmp(cur+2,"'qg",3)) return 1; } else { /* Handle CDATA blocks */ if (htmlstate == 0 && !strncasecmp(cur,"<![CDATA[",9)) { htmlstate = HS_IN_CDATA; cur += 9; continue; } if (htmlstate == HS_IN_CDATA && !strncmp(cur,"]]>",3)) { htmlstate = 0; cur += 3; continue; } /* Handle <!-- --> blocks (this depends on rendering mode, but hey). */ if (htmlstate == 0 && !strncmp(cur,"<!--",4)) { htmlstate = HS_IN_COMM; cur += 4; continue; } if (htmlstate == HS_IN_COMM && !strncmp(cur,"-->",3)) { htmlstate = 0; cur += 3; continue; } /* Detect what could pass for tag opening / closure... */ if (htmlstate == 0 && *cur == '<' && (isalpha(cur[1]) || cur[1] == '!' || cur[1] == '?')) { htmlstate = HS_IN_TAG; cur++; continue; } if (htmlstate == HS_IN_TAG && *cur == '>') { htmlstate = 0; htmlurl = 0; cur++; continue; } /* Handle double quotes around HTML parameters */ if (htmlstate == HS_IN_TAG && cur[-1] == '=' && *cur == '"') { htmlstate |= HS_IN_DBLQ; cur++; continue; } if (htmlstate == (HS_IN_TAG|HS_IN_DBLQ) && *cur == '"') { htmlstate = HS_IN_TAG; cur++; continue; } /* Handle single quotes around HTML parameters */ if (htmlstate == HS_IN_TAG && cur[-1] == '=' && *cur == '\'') { htmlstate |= HS_IN_SNGQ; cur++; continue; } if (htmlstate == (HS_IN_TAG|HS_IN_SNGQ) && *cur == '\'') { htmlstate = HS_IN_TAG; cur++; continue; } /* Special handling for SRC= and HREF= locations. */ if (htmlstate == HS_IN_TAG && isspace(cur[-1]) && !strncasecmp(cur,"href=",5)) { htmlurl = 1; cur += 5; continue; } if (htmlstate == HS_IN_TAG && isspace(cur[-1]) && !strncasecmp(cur,"src=",4)) { htmlurl = 1; cur += 4; continue; } /* Cancel mode if any character other than ", ', or qg: URL is encountered. */ if (htmlurl) htmlurl = 0; } cur++; } /* So, no XSS? Bummer. */ return 0;}/* Check for publicly cacheable documents. Returns 0 if not public, 1 if apparently meant to be public, 2 if partly protected. */static _u8 is_public(struct http_request* req, struct http_response* res) { _u8 http10intent; /* "Expires" and "Pragma" should say the same. */ if (res->pr10intent && res->ex10intent && res->pr10intent != res->ex10intent) return 2; http10intent = res->ex10intent ? res->ex10intent : res->pr10intent; /* HTTP/1.0 and HTTP/1.1 intents should say the same. */ if (http10intent && res->cc11intent && http10intent != res->cc11intent) return 2; /* [Picky] HTTP/1.0 and HTTP/1.1 intents should not appear at all, or appear at once */ if (picky_cache && (http10intent ^ res->cc11intent)) { if (strcmp(req->method,"GET")) return 0; /* Non-GET requests won't be cached. */ return 2; } if (res->cc11intent == INTENT_PRIV || http10intent == INTENT_PRIV) return 0; /* No interest in making this document private was expressed... */ if (strcmp(req->method,"GET")) return 0; /* Non-GET requests won't be cached. */ return 1;}static _u8 dump_fn[1024];static _u8 dumped_already;/* Save trace data to file, if requested. */static _u8* save_trace(struct http_request* req, struct http_response* res) { _s32 f; _u32 i; FILE* out; if (!trace_dir) return "-"; /* Do not save the same request twice. */ if (dumped_already) return dump_fn; dumped_already = 1; sprintf(dump_fn,"%.512s/%08x-%04x.trace",trace_dir,(_u32)time(0),getpid()); f = open(dump_fn, O_WRONLY | O_CREAT | O_EXCL, 0600); if (f < 0) { debug(">>> Unable to open trace file '%s'! <<<\n",dump_fn); return "-"; } out = fdopen(f,"w"); fprintf(out,"== REQUEST TO %s:%u (%u headers, %u byte payload) ==\n\n%s /%s%s%s HTTP/1.0\n", req->host, req->port, req->h.c, req->payload_len, req->method, req->path, req->query ? "?" : "", req->query ? req->query : (_u8*)""); for (i=0;i<req->h.c;i++) fprintf(out,"%s: %s\n", req->h.v1[i], req->h.v2[i]); fprintf(out,"\n"); if (req->payload_len) fwrite(req->payload,req->payload_len > MAXTRACEITEM ? MAXTRACEITEM : req->payload_len,1,out); if (req->payload_len > MAXTRACEITEM) fprintf(out,"\n*** DATA TRUNCATED DUE TO SIZE LIMITS ***"); fprintf(out,"\n\n== SERVER RESPONSE (%u headers, %u byte payload, detected MIME %s) ==\n\n" "HTTP/1.0 %u \n", res->h.c, res->payload_len, res->mime_type ? res->mime_type : (_u8*)"(none)", res->code); for (i=0;i<res->h.c;i++) fprintf(out,"%s: %s\n", res->h.v1[i], res->h.v2[i]); fprintf(out,"\n"); if (res->payload_len) fwrite(res->payload,res->payload_len > MAXTRACEITEM ? MAXTRACEITEM : res->payload_len,1,out); if (res->payload_len > MAXTRACEITEM) fprintf(out,"\n*** DATA TRUNCATED DUE TO SIZE LIMITS ***"); fprintf(out,"\n\n== END OF TRANSACTION ==\n"); fclose(out); close(f); return dump_fn;}/* Use Flare to decode Flash file, if available. */static void decode_flash(struct http_response* res) { _s32 f, pid; _u8 tmp[1024]; struct stat st; if (!dumped_already || !res->payload_len) return; /* ? */ sprintf(tmp,"%s.swf",dump_fn); f = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); if (f < 0) return; write(f, res->payload, res->payload_len); close(f); if (!(pid = fork())) { /* Flare is way too noisy, let's close stderr. */ close(2); execl("./flare","flare",tmp,NULL); execlp("flare","flare",tmp,NULL); exit(1); } if (pid > 0) waitpid(pid, (int*)&f, 0); unlink(tmp); sprintf(tmp,"%s.flr",dump_fn); if (stat(tmp,&st) || !st.st_size) unlink(tmp); /* So we should have a non-zero length .flr file next to a trace file now; ratproxy-report.sh will detect this. */}/* A "fuzzy" comparator to avoid reporting "refresher" cookies where some minor parameters were changed as new cookie arrivals; but to detect blanking or other major overwrites. */static _u8 unique_cookies(struct naive_list2* reqc, struct naive_list2* resc) { _u32 i,j; if (!resc->c) return 0; /* No cookies set at all. */ if (!reqc->c) return 1; /* All set cookies must be new. */ for (i=0;i<resc->c;i++) { for (j=0;j<reqc->c;j++) { if (!strcasecmp(resc->v1[i],reqc->v1[j]) && /* Same name */ strlen(resc->v2[i]) == strlen(reqc->v2[j])) /* Same length */ break; /* ...must be a refresher cookie. */ } /* No refresher cookie matches for one cookie? Good enough. */ if (j == reqc->c) return 1; } /* All cookies were refreshers. */ return 0;}/* Cookie renderer, for reporting purposes. */static _u8* make_cookies(struct naive_list2* reqc, struct naive_list2* resc) { _u8* ret = 0; _u32 i,j; _u8 had_some = 0; if (!resc->c) return "-";#define ALLOC_STRCAT(dest,src) do { \ _u32 _sl = strlen(src); \ _u32 _dl = 0; \ if (dest) _dl = strlen(dest); \ dest = realloc(dest,_sl + _dl + 1); \ if (!dest) fatal("out of memory"); \ strcpy(dest + _dl, src); \ } while (0) for (i=0;i<resc->c;i++) { /* Render only newly set cookies! */ for (j=0;j<reqc->c;j++) { if (!strcasecmp(resc->v1[i],reqc->v1[j]) && /* Same name */ strlen(resc->v2[i]) == strlen(reqc->v2[j])) /* Same length */ break; /* ...must be a refresher cookie. */ } if (j == reqc->c) { if (!had_some) had_some = 1; else ALLOC_STRCAT(ret,"; "); ALLOC_STRCAT(ret,resc->v1[i]); ALLOC_STRCAT(ret,"="); ALLOC_STRCAT(ret,resc->v2[i]); } } return ret ? ret : (_u8*)"-";}/* Check for safe JSON prologues. */static _u8 is_json_safe(_u8* str) { _u32 i = 0; while (json_safe[i]) { if (!strncmp(str,json_safe[i],strlen(json_safe[i]))) return 1; i++; } return 0;}/* Check for scripts that appear to be standalone or empty (as opposed to JSON-like dynamically generated response snippets for on-page execution). */static _u8 standalone_script(_u8* str) { if (!str) return 1; /* Empty */skip_more: while (*str && isspace(*str)) str++; if (!strncmp(str,"/*",2)) { str = strstr(str+2, "*/"); if (!str) return 1; /* Empty */ goto skip_more; } if (!strncmp(str,"//",2)) { str += 2; while (*str && strchr("\r\n",*str)) str++; goto skip_more; } if (*str == '(') { str++; goto skip_more; } if (!*str) return 1; /* Empty */ /* This is not very scientific - in fact, there is no good way to settle this - but should be a pretty good predictor in most cases. */ if (!strncasecmp(str,"var",3) && isspace(str[3])) return 1; /* Script */ if (!strncasecmp(str,"function",8) && isspace(str[8])) return 1; /* Script */ return 0; /* Probably JSON */}/* The main request handling and routing routine. */static void handle_client(FILE* client) { FILE *server; struct http_request* req; struct http_response* res; _u8 m; _u32 i; _u8 got_xss = 0;#define BEST_MIME (res->sniffed_mime ? res->sniffed_mime : \ (res->mime_type ? res->mime_type : (_u8*)"")) /* TODO: Ideally, S() shouldn't do HTML escaping in machine output (just filter | and control chars); but this requires ratproxy-report.sh to be reworked. */// Request printer macros - since most of the data does not change.#define SHOW_REF_MSG(warn,mesg,mod) \ sayf("%u|%u|%s|-|%u|%u|%s|http%s://%s:%u/%s%s%s|-|%s|-|%s|-|-|-\n", \ warn, mod, mesg, res->code, res->payload_len, res->mime_type ? \ res->mime_type : (_u8*)"-", req->from_ssl ? "s" : "", S(req->host,0), req->port, \ S(req->path,0), req->query ? "?" : "", req->query ? \ S(req->query,0) : (_u8*)"", save_trace(req,res), S(req->referer,0))#define SHOW_MSG(warn,mesg,off_par,mod) \ sayf("%u|%u|%s|%s|%u|%u|%s|%s|%s|%s|%s|http%s://%s:%u/%s%s%s|%s|%s|%s\n", \ warn, mod ,mesg, off_par ? S(off_par,0) : (_u8*)"-", \ res->code, res->payload_len, \ res->mime_type ? S(res->mime_type,0) : (_u8*)"-", \ res->sniffed_mime ? S(res->sniffed_mime,0) : (_u8*)"-", \ res->charset ? S(res->charset,0) : (_u8*)"-", \ save_trace(req,res), \ S(req->method,0), req->from_ssl ? "s" : "", S(req->host,0), \ req->port, S(req->path,0), req->query ? "?" : "", \ req->query ? S(req->query,0) : (_u8*)"", \ S(make_cookies(&req->cookies,&res->cookies),0), \ req->payload_len ? S(stringify_payload(req),0) : (_u8*)"-", \ res->payload_len ? S(res->payload,0) : (_u8*)"-") /* First, let's collect and complete the request */ req = collect_request(client,0,0); server = open_server_complete(client, req); if (req->is_connect) { ssl_setup(); ssl_start(fileno(server),fileno(client)); fclose(client); fclose(server); client = fdopen(ssl_cli_tap,"w+"); server = fdopen(ssl_srv_tap,"w+"); if (!client || !server) fatal("out of memory"); req = collect_request(client, req->host, req->port); } res = send_request(client, server, req, 0); send_response(client,res); if (req->from_ssl) ssl_shutdown(); /* Now, if the target is not within the set of tested domains, there are several things we want to check if it originated from within the tested locations. */ if (!host_ok(req->host)) { _u8 *refq; if (!req->ref_host) goto skip_tests; /* Requests between non-analyzed sites do not concern us. */ if (!host_ok(req->ref_host)) goto skip_tests; /* Referer token leakage test: contains_token() succeeds on "Referer" query */ if ((refq=strchr(req->referer,'?'))) { struct naive_list_p p = { 0, 0, 0, 0, 0 }; _u32 i; parse_urlencoded(&p,refq + 1); for (i=0;i<p.c;i++) if (contains_token(p.v1[i],p.v2[i])) break; if (i != p.c) SHOW_REF_MSG(3,"Referer may leak session tokens",1); } /* Cross-domain script inclusion check */ detect_mime(res); if (rp_strcasestr(BEST_MIME,"script") || !strcasecmp(BEST_MIME,"application/json")|| !strcasecmp(BEST_MIME,"text/css")) SHOW_REF_MSG(3,"External code inclusion",1); /* POST requests between domains - outgoing. */ if (strcmp(req->method,"GET")) { SHOW_REF_MSG(2,"Cross-domain POST requests",0); } else if (log_active) { i = 0; while (active_mime[i]) { if (!strcasecmp(BEST_MIME,active_mime[i])) { SHOW_REF_MSG(1,"References to external active content",1); break; } i++; } } goto skip_tests; } /* All right, everything below pertains to checks on URLs within the tested domain. Let's do some basic information gathering first. */ checksum_response(res); detect_mime(res); if (res->is_text) detect_charset(res); if (dump_urls) SHOW_MSG(0,"!All visited URLs",0,0); /* If requested to do so, we need to log non-HTTPS traffic and prioritize it depending on document type. */ if (log_mixed && !req->from_ssl) { m = get_modifiers(req,res);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -