Ack, there were some regex problems in the robots.txt parser, the attached
version should work properly, even though it does resort to a more
brute-force approach.
last response to myself for the day, promise ;)
-HD
##
# This plugin was written by H D Moore <[EMAIL PROTECTED]>
##
if(description)
{
# script_id(????);
script_version ("$Revision: 1.2 $");
name["english"] = "Directory Scanner";
script_name(english:name["english"]);
desc["english"] = "None Yet";
script_description(english:desc["english"]);
summary["english"] = "Directory Scanner";
script_summary(english:summary["english"]);
script_category(ACT_GATHER_INFO);
script_copyright(english:"This script is Copyright (C) 2002 Digital Defense
Inc.");
family["english"] = "Misc.";
script_family(english:family["english"]);
script_dependencie("find_service.nes");
script_require_ports(80);
exit(0);
}
function check_req (port, url)
{
soc = open_sock_tcp(port);
if (!soc) return (0);
req = http_get(item:url, port:port);
send(socket:soc, data:req);
http_resp = recv(socket:soc, length:16384);
close (soc);
return(string(http_resp));
}
function check_dir_list (dir)
{
for (CDC=0; dirs[CDC]; CDC=CDC+1)
{
if (dirs[CDC] == dir)
{
return(1);
}
}
return(0);
}
dirs[0] = ".cobalt";
dirs[1] = "1";
dirs[2] = "10";
dirs[3] = "2";
dirs[4] = "3";
dirs[5] = "4";
dirs[6] = "5";
dirs[7] = "6";
dirs[8] = "7";
dirs[9] = "8";
dirs[10] = "9";
dirs[11] = "AdminWeb";
dirs[12] = "Admin_files";
dirs[13] = "Administration";
dirs[14] = "CVS";
dirs[15] = "DMR";
dirs[16] = "HB";
dirs[17] = "HBTemplates";
dirs[18] = "Images";
dirs[19] = "Log";
dirs[20] = "Mail";
dirs[21] = "PDG_Cart";
dirs[22] = "Stats";
dirs[23] = "StoreDB";
dirs[24] = "Templates";
dirs[25] = "WebShop";
dirs[26] = "WebTrend";
dirs[27] = "Web_store";
dirs[28] = "_backup";
dirs[29] = "_notes";
dirs[30] = "_old";
dirs[31] = "_private";
dirs[32] = "a";
dirs[33] = "access";
dirs[34] = "account";
dirs[35] = "accounting";
dirs[36] = "adm";
dirs[37] = "admin";
dirs[38] = "admin-bak";
dirs[39] = "admin-old";
dirs[40] = "admin.back";
dirs[41] = "administration";
dirs[42] = "administrator";
dirs[43] = "adminweb";
dirs[44] = "analog";
dirs[45] = "app";
dirs[46] = "applets";
dirs[47] = "apps";
dirs[48] = "archive";
dirs[49] = "archives";
dirs[50] = "asp";
dirs[51] = "atc";
dirs[52] = "b";
dirs[53] = "back";
dirs[54] = "backend";
dirs[55] = "backup";
dirs[56] = "bak";
dirs[57] = "bank";
dirs[58] = "banner";
dirs[59] = "beta";
dirs[60] = "billpay";
dirs[61] = "bin";
dirs[62] = "buy";
dirs[63] = "buynow";
dirs[64] = "c";
dirs[65] = "cache-stats";
dirs[66] = "cart";
dirs[67] = "ccard";
dirs[68] = "ccards";
dirs[69] = "cgi";
dirs[70] = "cgi-bin";
dirs[71] = "cgi-csc";
dirs[72] = "cgi-local";
dirs[73] = "cgi-win";
dirs[74] = "cgis";
dirs[75] = "config";
dirs[76] = "core";
dirs[77] = "counter";
dirs[78] = "credit";
dirs[79] = "cron";
dirs[80] = "crons";
dirs[81] = "css";
dirs[82] = "customers";
dirs[83] = "cvsweb";
dirs[84] = "d";
dirs[85] = "dat";
dirs[86] = "data";
dirs[87] = "database";
dirs[88] = "db";
dirs[89] = "dbase";
dirs[90] = "design";
dirs[91] = "dev";
dirs[92] = "devel";
dirs[93] = "development";
dirs[94] = "dl";
dirs[95] = "doc";
dirs[96] = "doc-html";
dirs[97] = "docs";
dirs[98] = "down";
dirs[99] = "download";
dirs[100] = "downloads";
dirs[101] = "dump";
dirs[102] = "e";
dirs[103] = "eforum";
dirs[104] = "email";
dirs[105] = "emailclass";
dirs[106] = "employees";
dirs[107] = "empoyees";
dirs[108] = "error";
dirs[109] = "estmt";
dirs[110] = "etc";
dirs[111] = "exc";
dirs[112] = "exchange";
dirs[113] = "exe";
dirs[114] = "f";
dirs[115] = "file";
dirs[116] = "files";
dirs[117] = "forms";
dirs[118] = "forum";
dirs[119] = "fpadmin";
dirs[120] = "ftp";
dirs[121] = "g";
dirs[122] = "guestbook";
dirs[123] = "guests";
dirs[124] = "hidden";
dirs[125] = "hide";
dirs[126] = "hit_tracker";
dirs[127] = "hitmatic";
dirs[128] = "home";
dirs[129] = "htbin";
dirs[130] = "htdocs";
dirs[131] = "html";
dirs[132] = "ibank";
dirs[133] = "ibill";
dirs[134] = "icons";
dirs[135] = "idea";
dirs[136] = "ideas";
dirs[137] = "imagery";
dirs[138] = "img";
dirs[139] = "imp";
dirs[140] = "import";
dirs[141] = "inc";
dirs[142] = "include";
dirs[143] = "includes";
dirs[144] = "incoming";
dirs[145] = "info";
dirs[146] = "install";
dirs[147] = "intranet";
dirs[148] = "java";
dirs[149] = "jave";
dirs[150] = "jdbc";
dirs[151] = "js";
dirs[152] = "lib";
dirs[153] = "libraries";
dirs[154] = "library";
dirs[155] = "links";
dirs[156] = "loader";
dirs[157] = "log";
dirs[158] = "logfile";
dirs[159] = "logfiles";
dirs[160] = "logger";
dirs[161] = "logging";
dirs[162] = "login";
dirs[163] = "logon";
dirs[164] = "logs";
dirs[165] = "mail";
dirs[166] = "mall_log_files";
dirs[167] = "manual";
dirs[168] = "marketing";
dirs[169] = "msql";
dirs[170] = "mysql";
dirs[171] = "new";
dirs[172] = "odbc";
dirs[173] = "old";
dirs[174] = "oracle";
dirs[175] = "order";
dirs[176] = "orders";
dirs[177] = "outgoing";
dirs[178] = "pages";
dirs[179] = "passport";
dirs[180] = "password";
dirs[181] = "passwords";
dirs[182] = "perl";
dirs[183] = "perl5";
dirs[184] = "phorum";
dirs[185] = "php";
dirs[186] = "phpMyAdmin";
dirs[187] = "poll";
dirs[188] = "polls";
dirs[189] = "postgres";
dirs[190] = "priv";
dirs[191] = "private";
dirs[192] = "prv";
dirs[193] = "pub";
dirs[194] = "public";
dirs[195] = "purchase";
dirs[196] = "purchases";
dirs[197] = "pw";
dirs[198] = "rdp";
dirs[199] = "register";
dirs[200] = "registered";
dirs[201] = "reports";
dirs[202] = "reseller";
dirs[203] = "restricted";
dirs[204] = "retail";
dirs[205] = "root";
dirs[206] = "sales";
dirs[207] = "script";
dirs[208] = "scripts";
dirs[209] = "search";
dirs[210] = "secret";
dirs[211] = "sell";
dirs[212] = "setup";
dirs[213] = "shop";
dirs[214] = "shopper";
dirs[215] = "site";
dirs[216] = "software";
dirs[217] = "source";
dirs[218] = "sql";
dirs[219] = "staff";
dirs[220] = "stat";
dirs[221] = "statistic";
dirs[222] = "statistics";
dirs[223] = "stats";
dirs[224] = "status";
dirs[225] = "store";
dirs[226] = "support";
dirs[227] = "sys";
dirs[228] = "system";
dirs[229] = "tech";
dirs[230] = "temp";
dirs[231] = "templates";
dirs[232] = "test";
dirs[233] = "test-cgi";
dirs[234] = "testing";
dirs[235] = "tmp";
dirs[236] = "tools";
dirs[237] = "trafficlog";
dirs[238] = "tree";
dirs[239] = "updates";
dirs[240] = "usage";
dirs[241] = "user";
dirs[242] = "users";
dirs[243] = "usr";
dirs[244] = "ustats";
dirs[245] = "vfs";
dirs[246] = "web";
dirs[247] = "web800fo";
dirs[248] = "webadmin";
dirs[249] = "webboard";
dirs[250] = "webcart";
dirs[251] = "webcart-lite";
dirs[252] = "webdata";
dirs[253] = "webimages";
dirs[254] = "webimages2";
dirs[255] = "weblog";
dirs[256] = "weblogs";
dirs[257] = "website";
dirs[258] = "webstats";
dirs[259] = "wstats";
dirs[260] = "wusage";
dirs[261] = "www";
dirs[262] = "www-sql";
dirs[263] = "wwwjoin";
dirs[264] = "wwwlog";
dirs[265] = "wwwstats";
dirs[266] = "zipfiles";
# this needs to be updated to match the above list
dirs_last = 266;
# these are the strings used by the 404 checks
errmsg[0] = "not found";
errmsg[1] = "404";
errmsg[2] = "error has occurred";
errmsg[3] = "firewall-1 message";
errmsg[4] = "Reload acp_userinfo database";
errmsg[5] = "IMail Server Web Messaging";
errmsg[6] = "HP Web JetAdmin";
errmsg[7] = "Error processing SSI file";
errmsg[8] = "ExtendNet DX Configuration";
errmsg[9] = "not available";
errmsg[10] = "Content-Length: 0";
debug = 1;
if(debug) display("\n::[ DDI Directory Scanner running in debug mode\n::\n");
report = string("The following directories were discovered: ");
found = 0;
authreport = string("The following directories require authentication: ");
authfound = 0;
fake404 = string("");
Check200 = 1;
Check401 = 1;
Check403 = 1;
port = get_kb_item("Services/www");
if(!port)port = 80;
if(!get_port_state(port))
{
if(debug) display(":: Error: port ", port, " was not open on target.\n");
exit(0);
}
##
# pull the robots.txt file
##
soc = open_sock_tcp(port);
if (!soc)
{
if(debug) display(":: Error: could not connect to port ", port, " on target.\n");
exit(0);
}
display(":: Checking for robots.txt...\n");
req = http_get(item:"/robots.txt", port:port);
send(socket:soc, data:req);
http_line = recv_line(socket:soc, length:16384);
if (ereg(pattern:"HTTP/1.[01] 200", string:http_line))
{
while (http_line = recv_line(socket:soc, length:16384))
{
if (ereg(pattern:"disallow:", string:http_line, icase:TRUE))
{
# yes, i suck at regex's in nasl. I want my \s+!
robot_dir = ereg_replace(pattern:"disallow:\W*(/.*)\W$", string:http_line,
replace:"\1", icase:TRUE);
robot_dir = ereg_replace(pattern:"/(.*)(\W*)$", string:robot_dir,
replace:"\1", icase:TRUE);
robot_dir = ereg_replace(pattern:"/$|\?$", string:robot_dir, replace:"",
icase:TRUE);
if (debug) display(":: Discovered directory through robots.txt: '",
robot_dir, "'\n");
if (! check_dir_list(dir:robot_dir))
{
# add directory to the list
dirs_last = dirs_last + 1;
dirs[dirs_last] = robot_dir;
if (debug) display(":: Directory '", robot_dir, "' added to test
list\n");
} else {
if (debug) display(":: Directory '", robot_dir, "' already exists in
test list\n");
}
}
}
}
close(soc);
##
# test for servers which return 200/403/401 for everything
##
soc = open_sock_tcp(port);
if (!soc)
{
if(debug) display(":: Error: could not connect to port ", port, " on target.\n");
exit(0);
}
display(":: Checking for non-standard server responses\n");
req = http_get(item:"/NonExistent/", port:port);
send(socket:soc, data:req);
http_resp = recv(socket:soc, length:16384);
close (soc);
if(ereg(pattern:"HTTP/1.[01] 200", string: http_resp))
{
fake404 = 0;
if(debug) display(":: This server returns 200 for non-existent directories.\n");
for(i=0;errmsg[i];i=i+1)
{
if (ereg(pattern:errmsg[i], string:http_resp, icase:TRUE))
{
fake404 = errmsg[i];
if(debug) display(":: Using '", fake404, "' as an indication of a 404
error\n");
}
}
if (!fake404)
{
if(debug) display(":: Could not find an error string to match against for the
fake 404 response.\n");
if(debug) display(":: Checks which rely on 200 responses are being
disabled\n");
Check200 = 0;
}
} else {
fake404 = string("BadString0987654321*DDI*");
}
if(ereg(pattern:"HTTP/1.[01] 401", string: http_resp))
{
if(debug) display(":: This server requires authentication for non-existent
directories, disabling 401 checks.\n");
Check401 = 0;
}
if(ereg(pattern:"HTTP/1.[01] 403", string: http_resp))
{
if(debug) display(":: This server returns a 403 for non-existent directories,
disabling 403 checks.\n");
Check403 = 0;
}
display(":: Starting the directory scan...\n");
for(i=0;dirs[i];i=i+1)
{
res = check_req(port:port, url:string("/", dirs[i], "/"));
if( Check200 &&
ereg(pattern:"HTTP/1.[01] 200", string:res) &&
! (ereg(pattern:fake404, string:res, icase:TRUE))
)
{
if(debug) display(":: Discovered: " , dirs[i], "\n");
if(found)
{
report = report + ", /" + dirs[i];
found=found+1;
} else {
report = report + "/" + dirs[i];
}
found=found+1;
}
if(Check403 && ereg(pattern:"HTTP/1.[01] 403", string: res))
{
if (debug) display(":: Got a 403 for ", dirs[i], ", checking for file in the
directory...\n");
res2 = check_req(port:port, url:string("/", dirs[i], "/NonExistent.html"));
if(ereg(pattern:"HTTP/1.[01] 403", string:res2))
{
# the whole directory appears to be protected
if (debug) display(":: 403 applies to the entire directory \n");
} else {
if (debug) display(":: 403 applies to just directory indexes \n");
# the directory just has indexes turned off
if(debug) display(":: Discovered: " , dirs[i], "\n");
if(found)
{
report = report + ", /" + dirs[i];
} else {
report = report + "/" + dirs[i];
}
found=found+1;
}
}
if(Check401 && ereg(pattern:"HTTP/1.[01] 401", string: res))
{
if (debug) display(":: Got a 401 for ", dirs[i], "\n");
if(found)
{
authreport = authreport + ", /" + dirs[i];
} else {
authreport = authreport + "/" + dirs[i];
}
authfound=authfound+1;
}
}
result = string("");
if (found)
{
result = report;
}
if (authfound)
{
result = result + string("\n", authreport);
}
if (strlen(result))
{
security_note(port:port, data:result);
}