Scan all FTP accounts for vulnerabilities


#1

After a recent site intrusion, DH Support gave me a list of files in FTP accounts that had wide open 777 permissions, which I corrected. I know how to use the find command to locate files with a certain permission, but how do you scan all the site’s 18 ftp accounts in one step? The ftp accounts’ folders don’t seem to appear under the web site home. If I can figure out a way to search everything for outlying permissions, I’ll add that to my site periodic maintenance.


#2

I can offer some suggestions that should help if you are using Windows on your local machine.

The only labour-intensive part is to set up your installation of PuTTY so that it has a “saved session” named for each of your users, which does passwordless login (using .ssh). You only have to set this up once (except, you have to remember to add a further “saved session” whenever you add another user to your account).

Also, obtain a Dreamhost API key, let us call it MYKEY.

Then, to do something to each user,

step 1 is to get a current list of your users (choose some user, it doesn’t matter which one, to do the work, let us call it SOMEUSER) - the .bat file is

@ echo curl -s "https://api.dreamhost.com/?key=MYKEY&cmd=user-list_users_no_pw&format=tab" ^| grep -E $HOSTNAME ^| awk '{print $2}' > %temp%\temp.sh @ plink -load SOMEUSER -m %temp%\temp.sh > users.txt

and step 2 is to use this list of users to do something to each user; let us suppose that you want to execute the commands in your local file DOSOMETHING.SH; the .bat file is

So, for your case, the file DOSOMETHING.SH could say something like


#3

a little research gave me this


#4

Thanks. But I already said I know how to use find with the -perm option. The challenge would be do do it for 18 different ftp accounts.
[hr]

I use putty on Windows. But these are FTP only accounts. I can’t figure out a way to get putty connected to them.

I wonder if there is a way to script the from my local Linux desktop or from my DH shell account. DH Support did it somehow.


#5

One step might be difficult if all your users are locked down to FTP Only (which is a good thing). Crontab might be suitable - you could probably write “one solution” then copy/pasta your method.


#6

Then how would you feel about placing a .php file at a fixed place in each website (gambling on “security through obscurity”) e.g.

and having on your local machine a .bat file which does

for each website? I suppose you would say that is horrible.


#7

Hmmm, I just thought that since DH support was able to scan all my FTP accounts that there was some way to access them all from ~ that I couldn’t see.

I can see how I could write a shell script to open each FTP account in turn, but I don’t see a way to pass an system command like “find . -type f -perm 777” out of the FTP connection.

I’ve had some fun with the python ftplib recently. It would be pretty cool to set up some lists of the ftp users and passwords, then loop though them. But as with shell, I don’t see anything in the python ftp module that handles calls to the OS so I could insert a find command.

DH support must have something already built that does this.


#8

Yeah, Support can sudo a privileged account to gain access to the entire file system.


#9

“Cool” might be putting it too strongly.

Last time this came up, I argued that as Dreamhost can make these kinds of integrity check so much more easily and securely than most customers can, we should ask them to do it. I was surprised to find no one agreed with me and several people strongly disagreed.

Apparently it would be molly-coddling if Dreamhost were to do this kind of thing for their customers.

Well let’s see what we can do without being molly-coddled. My intuition is saying it will be horrible. Maybe this is the best way of proving it would be better if Dreamhost did it.

Here’s what I can think of so far:

(1) get list of domains from Dreamhost API
(2) get list of ftp-only users from Dreamhost API
(3) scan (1) using (2) to obtain a list of websites on ftp-only users
(4) for each website on this list, (a) use ftp to upload a php file (b) use curl to run it © use ftp to delete it

all packaged up into a single script. (eg a .bat file if on Windows, or something that a cron job could do).

If anyone can think of a better way, please say so! Otherwise I will give it a try (as it’s the weekend).

An alternative could be to change the ftp-only users into shell users and change them back afterwards. The problem would be that if anything goes wrong, users could be left in a non-standard state. If anything goes wrong with the steps suggested above, the worst that would happen is some php scripts would be left lying around, but as the scripts only do integrity checking, they would be relatively harmless.


#10

Well the following appears to work, on Windows. It’s as least as horrible as I thought it would be. Unless there is a much better way that a customer can do this, it is evidence that this kind of thing would be better taken care of at a more privileged level by Dreamhost.

The .bat file takes three parameters:

parameter 1 is the name of a shell user (doesn’t matter which one) for using as a workhorse; the local installation of PuTTY should have a ‘saved session’ named for this user which can do passwordless login;

parameter 2 is a dreamhost api key that can do domain-list_domains and user-list_users_no_pw;

parameter 3 is the password of the account’s ftp-only users.

I’ve made one simplifying assumption, which is that all of the account’s ftp-only users have the same password. If that’s not the case, we would need to modify what follows so that it prompts the user to type in the ftp password whenever needed.

@ rem scans the websites on ftp-only users on a dreamhost account for world-writable files @ @ rem parameter 1 is the name of a shell user for using as a workhorse @ rem parameter 2 is a dreamhost api key that can do domain-list_domains and user-list_users_no_pw @ rem parameter 3 is the password of the account's ftp-only users @ @ call domains %1 %2 @ call websites %1 %2 @ gawk "{ system (\"scanw \" $1 \" \" $2 \" %3 \") }" websites.txt

It uses three building-blocks:

(1) domains.bat creates in ‘domains.txt’ an edited list of the account’s domains and users;
(2) websites.bat reads ‘domains.txt’ and creates in ‘websites.txt’ an edited list of the account’s domains which are on ftp-only users;
(3) scanw.bat scans domain %1 on user %2 with password %3 for world-writable files.

Here are the building-blocks:

domains.bat

@ echo curl -s "https://api.dreamhost.com/?key=%2&cmd=domain-list_domains&format=tab" ^| grep -E $HOSTNAME ^| grep -E cgi ^| sed -e "s/ / ! /g" ^| sed -e "s/ / ! /g" ^| awk '{print $2 " " $7 " !" $7}' > %temp%\temp.sh @ plink -load %1 -m %temp%\temp.sh > domains.txt

websites.bat

@ echo curl -s "https://api.dreamhost.com/?key=%2&cmd=user-list_users_no_pw&format=tab" ^| grep -E ftponly ^| awk '{print "!" $2}' > %temp%\temp.sh @ plink -load %1 -m %temp%\temp.sh | gawk "{ system (\"grep -E \" $0 \" domains.txt\") }" | gawk "{ print $1 \" \" $2 }" > websites.txt

scanw.bat

@ echo scanning domain %1 (on ftp user %2) for world-writable files @ @ echo ^<?php $output = shell_exec('find . -type f -perm -o=w ^| grep . ^|^| echo %1 has no world-writable files'); echo "$output"; ?^> > %temp%\temp.php @ @ echo user %2> %temp%\temp.txt @ echo %3>> %temp%\temp.txt @ echo cd %1>> %temp%\temp.txt @ echo lcd %temp%>> %temp%\temp.txt @ echo put temp.php>> %temp%\temp.txt @ echo bye>> %temp%\temp.txt @ ftp -n -s:%temp%\temp.txt %1 > nul @ @ curl -s http://%1/temp.php @ @ echo user %2> %temp%\temp2.txt @ echo %3>> %temp%\temp2.txt @ echo cd %1>> %temp%\temp2.txt @ echo delete temp.php>> %temp%\temp2.txt @ echo bye>> %temp%\temp2.txt @ ftp -n -s:%temp%\temp2.txt %1 > nul

I think it needs three comments:

(1) the ‘sed’ commands in ‘domains.bat’ are inserting a printable character (’!’ in this case, but it doesn’t matter what) between consecutive tabs, in order to normalize the tabular output so that awk can count the arguments

(2) we prepend an unlikely character (again ‘!’) to one copy of the user name and then search on that, in order to avoid spurious matches between a user name and part of a domain name

(3) the version of the ftp client on my machine is rather old and not very capable, which is why I have to invoke it in such a complicated way; your version may allow a simpler invocation.


#11

Here is a slightly simpler version. Yesterday’s version called the https dreamhost api from the server because curl objected to the certificate when I ran it on my local machine. Now I’ve discovered the -k option to curl which turns off certificate-checking.

So we no longer need the workhorse shell user (and thus no need for PuTTY or plink either). I’ve also changed from ‘sed’ to ‘gawk’ (which unlike sed recognizes \t for tab) thus avoiding having explicit tab characters in the script (which don’t show up very well when posted to this forum).

This version takes two parameters:

parameter 1 is a dreamhost api key that can do domain-list_domains and user-list_users_no_pw;
parameter 2 is the password of the account’s ftp-only users.

I’ve made one simplifying assumption, which is that all of the account’s ftp-only users have the same password. If that’s not the case, we would need to modify what follows so that it prompts the user to type in the ftp password whenever needed.

@ rem scans the websites on ftp-only users on a dreamhost account for world-writable files @ @ rem parameter 1 is a dreamhost api key that can do domain-list_domains and user-list_users_no_pw @ rem parameter 2 is the password of the account's ftp-only users @ @ call domains %1 @ call websites %1 @ gawk "{ system (\"scanw \" $1 \" \" $2 \" %2 \") }" websites.txt

It uses three building-blocks:

(1) domains.bat creates in ‘domains.txt’ an edited list of the account’s domains and users;
(2) websites.bat reads ‘domains.txt’ and creates in ‘websites.txt’ an edited list of the account’s domains which are on ftp-only users;
(3) scanw.bat scans domain %1 on user %2 with password %3 for world-writable files.

Here are the building-blocks:

domains.bat (now running curl on local machine and using gawk instead of sed)

websites.bat (now running curl on local machine)

scanw.bat (identical to previous version)

@ echo scanning domain %1 (on ftp user %2) for world-writable files @ @ echo ^<?php $output = shell_exec('find . -type f -perm -o=w ^| grep . ^|^| echo %1 has no world-writable files'); echo "$output"; ?^> > %temp%\temp.php @ @ echo user %2> %temp%\temp.txt @ echo %3>> %temp%\temp.txt @ echo cd %1>> %temp%\temp.txt @ echo lcd %temp%>> %temp%\temp.txt @ echo put temp.php>> %temp%\temp.txt @ echo bye>> %temp%\temp.txt @ ftp -n -s:%temp%\temp.txt %1 > nul @ @ curl -s http://%1/temp.php @ @ echo user %2> %temp%\temp2.txt @ echo %3>> %temp%\temp2.txt @ echo cd %1>> %temp%\temp2.txt @ echo delete temp.php>> %temp%\temp2.txt @ echo bye>> %temp%\temp2.txt @ ftp -n -s:%temp%\temp2.txt %1 > nul

I think it needs three comments:

(1) the ‘gawk’ commands in ‘domains.bat’ are inserting a printable character (here ‘-’, but it doesn’t matter what) between consecutive tabs, in order to normalize the tabular output so that the last ‘gawk’ can count the arguments

(2) we prepend an unlikely character (’!’) to one copy of the user name and then search on that, in order to avoid spurious matches between a user name and part of a domain name

(3) the version of the ftp client on my machine is rather old and not very capable, which is why I have to invoke it in such a complicated way; your version may allow a simpler invocation.


#12

Wow, thanks. I haven’t used the Dreamhost API, but looked up how to get it. So this version doesn’t need the API?

This runs on a local Windows PC?


#13

Hi, yes that version is for Windows (but still does use the Dreamhost api).

Here is a version that runs purely on the server. It could be put in a cron job … except that it prompts the user for the ftp password whenever needed. I couldn’t find a way to run ftp on the server without it prompting for the password interactively. How is that done?

Anyway, this version takes one parameter, which is a Dreamhost api key, as follows:

#!/bin/bash mkdir ~/tmp 2>nul curl -s "https://api.dreamhost.com/?key=$1&cmd=domain-list_domains&format=tab" | awk '{gsub("\t"," ",$0); print;}' | gawk '{gsub(" "," - ",$0); print;}' | gawk '{gsub(" "," - ",$0); print;}' | awk '{print $2 " " $7 " !" $7}' > ~/tmp/domains.txt curl -s "https://api.dreamhost.com/?key=$1&cmd=user-list_users_no_pw&format=tab" | grep -E ftponly | awk '{print "!" $2}' | awk '{ system ("grep -E " $0 " ~/tmp/domains.txt") }' | awk '{print $1 " " $2}' > ~/tmp/websites.txt awk '{ system ("bash scanw.sh " $1 " " $2) }' ~/tmp/websites.txt

which calls scanw.sh:

#!/bin/bash echo \<?php \$output = shell_exec\(\'find . -type f -perm -o=w \| grep . \|\| echo $1 has no world-writable files\'\)\; echo \"\$output\"\; ?\> > ~/tmp/temp.php echo scanning domain $1 on ftp user $2 for world-writable files echo user $2> ~/tmp/temp.txt echo cd $1>> ~/tmp/temp.txt echo lcd ~/tmp>> ~/tmp/temp.txt echo put temp.php>> ~/tmp/temp.txt echo bye >> ~/tmp/temp.txt ftp -n $1 < ~/tmp/temp.txt > nul curl -s http://$1/temp.php echo user $2> ~/tmp/temp2.txt echo cd $1>> ~/tmp/temp2.txt echo delete temp.php>> ~/tmp/temp2.txt echo bye >> ~/tmp/temp2.txt ftp -n $1 < ~/tmp/temp2.txt > nul echo done scanning domain $1