I finally bit the bullet and finished this thing up today, looking for some 
feedback before I go any further.

The point of this script is to locate valid directories in the web root of a 
server. The list of discovered directories will be saved in the KB and be 
used by any plugins which need this information. One good example would be a 
plugin which looks for copies of WS_FTP.LOG in the web root, it could dimply 
depend on this plugin, then iterate through all directories in the KB looking 
for WS_FTP.LOG in each directory. Another example would be a plugin which 
looks for directory indexes.

With some minor modifications to webmirror.nasl, it could use the list of 
directories found by this plugin to perform a much more complete crawl. The 
results of the crawl could be used to even further populate the directory 
list in the KB. The way I would like to see this implemented:

[DDI_Directory_Scanner.nasl] -> [webmirror.nasl] -> [ws_ftp_log.nasl]

Where ws_ftp_log has a script_dependencie() for webmirror and webmirror 
depends on DDI_Directory_Scanner. You could even flip that relationship and 
allow the directory scanner to search the subdirectories of those found in 
webmirror...

The current implementation uses 200, 403, and 401 responses to determine if a 
directory exists. Only the 200 and 403 responses are considered when 
populating the KB tree (not implemented yet).  There is a built-in 
"no404.nasl"-like check which looks for common error messages and works 
around them, for servers which respond with a 200 OK for non-existent 
directories. The 403 check will try to verify whether the directory itself is 
protected or whether just directory indexes are disabled and there is no 
default page. 

The first chunk of the directory array was taken from nikto and whisker, I 
browsed a few dozen vhosts on some colo machines I have access to and 
populated it further based on what I found. PLEASE submit more directories if 
you can and custom 404 messages that arent already handled. 

I would like to take this one further and recursively search the 
subdirectories of those already found, but I wanted to get something out 
today for you all to play with. 

Please test using the 'nasl' command line interpreter only please, there is no 
script_id assigned so just dropping it into your plugin directory wont do 
much do good. Here is an example run (apologies to Renaud):

hdm@masada:~> nasl DDI_Directory_Scanner.nasl -t www.nessus.org
** WARNING : packet forgery will not work
** as NASL is not running as root
DDI_Directory_Scanner.nasl : Warning : evaluating unknown variable - 
description
Discovered: doc
Discovered: icons
The following directories were discovered: /doc, /icons

-HD
##
#   This plugin was written by H D Moore <[EMAIL PROTECTED]>
##


if(description)
{
        # script_id(????);
        script_version ("$Revision: 1.0 $");
 
        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));    
}

dirs[0] = "Admin_files";
dirs[1] = "Administration";
dirs[2] = "DMR";
dirs[3] = "Mail";
dirs[4] = "PDG_Cart";
dirs[5] = "Stats";
dirs[6] = "StoreDB";
dirs[7] = "WebShop";
dirs[8] = "WebTrend";
dirs[9] = "Web_store";
dirs[10] = "_backup";
dirs[11] = "_old";
dirs[12] = "_private";
dirs[13] = "access";
dirs[14] = "account";
dirs[15] = "accounting";
dirs[16] = "admin";
dirs[17] = "administration";
dirs[18] = "administrator";
dirs[19] = "analog";
dirs[20] = "app";
dirs[21] = "applets";
dirs[22] = "apps";
dirs[23] = "archive";
dirs[24] = "archives";
dirs[25] = "asp";
dirs[26] = "atc";
dirs[27] = "back";
dirs[28] = "backend";
dirs[29] = "backup";
dirs[30] = "bak";
dirs[31] = "banner";
dirs[32] = "beta";
dirs[33] = "bin";
dirs[34] = "buy";
dirs[35] = "buynow";
dirs[36] = "c";
dirs[37] = "cache-stats";
dirs[38] = "cart";
dirs[39] = "ccard";
dirs[40] = "core";
dirs[41] = "css";
dirs[42] = "dat";
dirs[43] = "data";
dirs[44] = "database";
dirs[45] = "db";
dirs[46] = "dbase";
dirs[47] = "design";
dirs[48] = "dev";
dirs[49] = "devel";
dirs[50] = "development";
dirs[51] = "doc";
dirs[52] = "doc-html";
dirs[53] = "docs";
dirs[54] = "down";
dirs[55] = "download";
dirs[56] = "downloads";
dirs[57] = "employees";
dirs[58] = "exe";
dirs[59] = "file";
dirs[60] = "files";
dirs[61] = "forum";
dirs[62] = "fpadmin";
dirs[63] = "ftp";
dirs[64] = "guestbook";
dirs[65] = "guests";
dirs[66] = "hidden";
dirs[67] = "hit_tracker";
dirs[68] = "hitmatic";
dirs[69] = "home";
dirs[70] = "htdocs";
dirs[71] = "html";
dirs[72] = "ibill";
dirs[73] = "icons";
dirs[74] = "idea";
dirs[75] = "ideas";
dirs[76] = "imp";
dirs[77] = "import";
dirs[78] = "includes";
dirs[79] = "incoming";
dirs[80] = "info";
dirs[81] = "install";
dirs[82] = "intranet";
dirs[83] = "java";
dirs[84] = "jdbc";
dirs[85] = "js";
dirs[86] = "lib";
dirs[87] = "library";
dirs[88] = "links";
dirs[89] = "loader";
dirs[90] = "logfile";
dirs[91] = "logfiles";
dirs[92] = "logger";
dirs[93] = "logging";
dirs[94] = "login";
dirs[95] = "mail";
dirs[96] = "manual";
dirs[97] = "mysql";
dirs[98] = "new";
dirs[99] = "old";
dirs[100] = "oracle";
dirs[101] = "order";
dirs[102] = "orders";
dirs[103] = "outgoing";
dirs[104] = "pages";
dirs[105] = "passport";
dirs[106] = "password";
dirs[107] = "passwords";
dirs[108] = "perl5";
dirs[109] = "php";
dirs[110] = "phpMyAdmin";
dirs[111] = "poll";
dirs[112] = "postgres";
dirs[113] = "private";
dirs[114] = "pub";
dirs[115] = "public";
dirs[116] = "purchase";
dirs[117] = "purchases";
dirs[118] = "pw";
dirs[119] = "register";
dirs[120] = "registered";
dirs[121] = "reports";
dirs[122] = "reseller";
dirs[123] = "restricted";
dirs[124] = "retail";
dirs[125] = "sales";
dirs[126] = "secret";
dirs[127] = "sell";
dirs[128] = "setup";
dirs[129] = "shop";
dirs[130] = "shopper";
dirs[131] = "site";
dirs[132] = "staff";
dirs[133] = "stat";
dirs[134] = "statistic";
dirs[135] = "statistics";
dirs[136] = "stats";
dirs[137] = "status";
dirs[138] = "store";
dirs[139] = "support";
dirs[140] = "sys";
dirs[141] = "system";
dirs[142] = "temp";
dirs[143] = "templates";
dirs[144] = "test";
dirs[145] = "testing";
dirs[146] = "tmp";
dirs[147] = "tools";
dirs[148] = "trafficlog";
dirs[149] = "tree";
dirs[150] = "updates";
dirs[151] = "user";
dirs[152] = "users";
dirs[153] = "ustats";
dirs[154] = "vfs";
dirs[155] = "web";
dirs[156] = "web800fo";
dirs[157] = "webadmin";
dirs[158] = "webboard";
dirs[159] = "webcart";
dirs[160] = "webcart-lite";
dirs[161] = "webdata";
dirs[162] = "webimages";
dirs[163] = "webimages2";
dirs[164] = "weblog";
dirs[165] = "weblogs";
dirs[166] = "website";
dirs[167] = "webstats";
dirs[168] = "wstats";
dirs[169] = "wusage";
dirs[170] = "www";
dirs[171] = "www-sql";
dirs[172] = "wwwjoin";
dirs[173] = "wwwlog";
dirs[174] = "wwwstats";
dirs[175] = "zipfiles";

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";

debug = 1;

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)) exit(0);


# first we test for servers which return 200/403/401 for everything
soc = open_sock_tcp(port);
if (!soc) exit(0);
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;
}



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, investigating...\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("The 403 applies to the entire directory \n");   
        } else {
            if (debug) display("The 403 applies to just directory indexes \n");
            
            # the directory just has indexes turned off
            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..\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);
}

Reply via email to