=^.^=

Using DFind.exe and Blocking w00tw00t.at.ISC.SANS.DFind

karma

Chances are you've seen something like this in your access/error_logs:

[Tue Jul 13 12:13:54 2010] [error] [client 74.63.218.250] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)
[Tue Jul 13 12:16:45 2010] [error] [client 74.63.218.250] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)
[Wed Jul 14 00:26:44 2010] [error] [client 62.103.39.74] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)
[Wed Jul 14 10:27:20 2010] [error] [client 74.63.218.250] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)
[Wed Jul 14 10:30:11 2010] [error] [client 74.63.218.250] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)

You can start breathing again - it's just the signature of a slightly outdated vulnerability scanner for windows-based script kiddies. I couldn't find anything particularly useful on it with Google so this article will show you not only how to block sources of the scans (they could be zombies equipped with other tools after all) but also how to use it.

You can either download DFind from its project homepage (requires an account) or you can grab it from http://foxpa.ws/dist/DFind_1.0.9.rar and extract it somewhere on a windows box. Your antivirus will probably have a fit since it's a common component of bundled malware, just make it shut up for a bit.

It doesn't come with much in the way of documentation, just a revision.txt file:

Usage:

DFind is a free command line scanner used to be small, fast, introducing a large number of command lines, helping anyone to identify
in one line the kiddie's doors.

Revision:

---x32.1.0.9---
+PestPatrol detects the last MD5 hash of DFind, quick binary update to bypass
+WEBDAV status scanner on IIS/5.0 added.
+Back to VC6 and old mfc, much smaller and stable
+Symantec detected signature gone; "\x20\x20VULNE" is enough for them to classify it as 'HackTool.DFind', this is updated to "\x20\x20VULN...", keep it up sym ;>
+"DFind -update" added to check like a lazy for new updates, this will calculate and compare the md5hash of the file your are using with the one I publish on the website.
---x32.1.0.8---
+No more using MFC libraries, too recents, too buggy...
+'HackTool.DFind' on Symantec 10.0.1.1000 gone
+New compilation moved under Visual Studio 2005 and Platorm SDK.
+x64.1.0.8 compilation available to the website (for the new 64-bit systems).
---------------

http://getdfind.class101.org (32-bit)
http://getdfind32.class101.org (32-bit)
http://getdfind64.class101.org (64-bit)

Note:

The author of this tool isn't responsible of the bad use of it, just try it on (and secure..) your network before the attacker does.

A.D

Pop open a command line and give it a whirl, you'll get a help menu similar to this:

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind.exe

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 MAIN MENU
 =============================================================

[+] Usage: DFind <option> <syntax>
[+] <option>:
 _______________________________________________________
 |___-p___|___+p___|___-pu__|__-ban__|__-web__|__-dde__|
 |__-wdv__|
 _______________________________________________________
 |__-rad__|__-wns__|_-http__|_-sock__|__-ipc__|__-nbn__|
 |__-vnc__|
 _____________
 |__-update__|

[+] Type DFind <option> to look the <syntax>
[+] Number of possibles <syntax>: 973

Here's the help listing for each flag:

================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 TCP port scanner
 =============================================================

[+]  . DFind -p 80 192.168.0.1  [-v]
[+]  . DFind -p 80 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -p 80 150 192.168.0.1  [THREADS] [-v]
[+]  . DFind -p 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -p 1,2,..,9,10 192.168.0.1  [-v]
[+]  . DFind -p 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind +p

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 PING port scanner
 =============================================================

[+]  . DFind +p 80 192.168.0.1  [-v]
[+]  . DFind +p 80 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind +p 80 150 192.168.0.1  [THREADS] [-v]
[+]  . DFind +p 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind +p 1,2,..,9,10 192.168.0.1  [-v]
[+]  . DFind +p 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -pu

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 UDP port scanner
 =============================================================

[+]  . DFind -pu 80 192.168.0.1  [-v]
[+]  . DFind -pu 80 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -pu 80 150 192.168.0.1  [THREADS] [-v]
[+]  . DFind -pu 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -pu 1,2,..,9,10 192.168.0.1  [-v]
[+]  . DFind -pu 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -ban

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 BANNER port scanner
 =============================================================

[+] . DFind -ban 80 192.168.0.1  [-v] [-spy "text"]
[+] . DFind -ban 80 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]
[+] . DFind -ban 80 150 192.168.0.1  [THREADS] [-v] [-spy "text"]
[+] . DFind -ban 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]
[+] . DFind -ban 1,.,10 192.168.0.1  [-v] [-spy "text"]
[+] . DFind -ban 1,.,10 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]

 [-spy "text"] == case sensitive

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -web

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 WEBSERVER BANNER port scanner
 =============================================================

[+] . DFind -web 192.168.0.1  [-v] [-spy "text"]
[+] . DFind -web 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]
[+] . DFind -web 80 192.168.0.1  [-v] [-spy "text"]
[+] . DFind -web 80 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]
[+] . DFind -web 80 150 192.168.0.1  [THREADS] [-v] [-spy "text"]
[+] . DFind -web 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]
[+] . DFind -web 1,.,10 192.168.0.1  [-v] [-spy "text"]
[+] . DFind -web 1,.,10 192.168.0.0 192.168.0.255  [THREADS] [-v] [-spy "text"]

 [-spy "text"] == case sensitive

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -dde

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Microsoft NETDDE port scanner
 -----------------------
 ripped from HOD exploit with a recoded nbname checking
 =============================================================

[+]  . DFind -dde 192.168.0.1  [-v]
[+]  . DFind -dde 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -rad

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Famatech LLC RAdmin 2.1 vulnerability scanner
 =============================================================

[+]  . DFind -rad 192.168.0.1  [-v|-vv]
[+]  . DFind -rad 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -rad 80 192.168.0.1  [-v|-vv]
[+]  . DFind -rad 80 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -rad 80 150 192.168.0.1  [THREADS] [-v|-vv]
[+]  . DFind -rad 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -rad 1,2,..,9,10 192.168.0.1  [-v|-vv]
[+]  . DFind -rad 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -nns

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==

[+] wrong command line, type DFind to view the main menu

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -wns

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Microsoft WINS MS04-045 vulnerability and os/sp scanner
 =============================================================

[+]  . DFind -wns 192.168.0.1  [-v|-vv]
[+]  . DFind -wns 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -http

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Anonymous/Transparent/High-Anon HTTP proxy scanner
 =============================================================

[+]  . DFind -http 192.168.0.1  [-v]
[+]  . DFind -http 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -http 80 192.168.0.1  [-v]
[+]  . DFind -http 80 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -http 80 150 192.168.0.1  [THREADS] [-v]
[+]  . DFind -http 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -http 1,2,..,9,10 192.168.0.1  [-v]
[+]  . DFind -http 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -sock

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Anonymous SOCKS5 proxy scanner
 =============================================================

[+]  . DFind -sock 192.168.0.1  [-v]
[+]  . DFind -sock 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -sock 80 192.168.0.1  [-v]
[+]  . DFind -sock 80 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -sock 80 150 192.168.0.1  [THREADS] [-v]
[+]  . DFind -sock 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v]
[+]  . DFind -sock 1,2,..,9,10 192.168.0.1  [-v]
[+]  . DFind -sock 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -ipc

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Microsoft NETBIOS NULL SESSION port scanner
 =============================================================

[+]  . DFind -ipc 192.168.0.1  [-v]
[+]  . DFind -ipc 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -nbn

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 Microsoft NETBIOS NAME port scanner
 -----------------------------
 ripped from HOD exploit with a recoded nbname checking
 =============================================================

[+]  . DFind -nbn 192.168.0.1  [-v]
[+]  . DFind -nbn 192.168.0.0 192.168.0.255  [THREADS] [-v]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -vnc

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 VNC4 systems vulnerability scanner
 =============================================================

[+]  . DFind -vnc 192.168.0.1  [-v|-vv]
[+]  . DFind -vnc 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -vnc 80 192.168.0.1  [-v|-vv]
[+]  . DFind -vnc 80 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -vnc 80 150 192.168.0.1  [THREADS] [-v|-vv]
[+]  . DFind -vnc 80 150 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]
[+]  . DFind -vnc 1,2,..,9,10 192.168.0.1  [-v|-vv]
[+]  . DFind -vnc 1,2,..,9,10 192.168.0.0 192.168.0.255  [THREADS] [-v|-vv]

 If ([THREADS]==NULL) thread=500, maximum allowed=2500

 visit class101.org for more informations

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind -update

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 DFind updates checker
 =============================================================

[+] -----Calculating the MD5 hash (actual binary:32-bit)-----
[+] "dfind.exe" => 3188826a5c39dd49d0ef3ce3d7bea1c2
[+] -----Retrieving the MD5 hash (official binary:32-bit)-----
[+] "official md5" => 3188826a5c39dd49d0ef3ce3d7bea1c2
[+]
[+] Your version is up to date, no updates required

As you can see it sports an automatic update feature and its small size makes it perfect for bundling in a trojan payload. If you guessed the -web flag was responsible for the log entries you win a gold star:

C:\Documents and Settings\Administrator\My Documents\Downloads>dfind.exe -web xxx.
xxx.xxx.xxx

 ================================================[rev-1.0.9]==
 ==============DFind - #1 Tiny Security Scanner===============
 ============multi-threaded for Linux and Windows=============
 ===================================================[32-bit]==
 WEBSERVER BANNER port scanner
 =============================================================

[+] status..: 100% thread(s):0
[+] results.: 1 / 5 PORT(s) / 1 IP(s) (open:1)

Showed up as this in my error_log:

[Wed Jul 14 15:56:55 2010] [error] [client yyy.yyy.yyy.yyy] client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)

Since this error is generated by requests missing the mandatory (in HTTP 1.1 but not 1.0) Host header it begs the question if this is just the result of sloppy programming or an intended signature. Either way we can take advantage of it to block hosts using the -web flag. BobA on the hostgator forums suggests blocking these requests with some mod_rewrite rules:

RewriteEngine on
# Block undesired user-agents
RewriteCond %{HTTP_USER_AGENT} ^w00tw00t.at.ISC.SANS.DFind
RewriteRule ^.*$ http://www.thenameofmydomain.com/403.html [L]

That doesn't really do us any good. Port scanning will still show an exposed web server, which is about all the output you get with the -web flag anyway. Chances are good that if you're being scanned by this thing it's not the only tool in the zombie's box either. We need something that will IP-block the bugger so they can't keep scanning.

We need a Snort rule:

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"WEB-MISC DFind Web Server Scan"; flow:to_server,established; uricontent:".at.ISC.SANS.DFind"; reference:foxpa.ws,301; classtype:web-application-attack; sid:4000001; rev:1; fwsam: src, 1 day;)

You'll notice I dropped the w00tw00t, it seems some folks are getting test0 and other prefixes and the chances of the rest of the string popping up in your URLs legitimately are fairly slim. Pop this into your snort ruleset somewhere (make sure the SID doesn't conflict with any other custom rules) and restart, with the addition of snortsam or a similar IPS add-on attackers should now find themselves automatically firewalled.

Securing /tmp the Cheap Hooker Way

karma

Numerous exploits take advantage of /tmp's open nature by uploading scripts or executables to it, then running them from it. While we may or may not be able to do something about the scripts being uploaded (not really possible in a shared hosting environment where any number of unknown clients' scripts and so on have to be run regardless of their flaws) we can certainly keep them from being run from within /tmp. The noexec mount option makes it impossible to run shell scripts or binaries directly from a filesystem that has been mounted with it. The nosuid option keeps people from using or setting the setuid flag on any files.

If you're not using LVM or you're working with virtual machines tossing on another partition might not be practical. Fortunately /tmp works just as well when mounted from a partition image file. Follow through these shell commands, replacing the count= value for dd to whatever size you would like to make the /tmp image in megs. You won't need much space, on a typical shared hosting server the most one tends to find in there is session data. You are welcome to make a sparse file image however the frequency of writes and deletes as well as the small required size makes this less practical than it might sound. I think 256 megs is a nice round number, unless you're doing something fancy that actually needs /tmp files.

# dd if=/dev/zero of=/tmp.img bs=1M count=256
# mke2fs -j /tmp.img
# chmod 600 /tmp.img
# mkdir /mnt/tmp
# mount -o loop /tmp.img /mnt/tmp
# mv /tmp/* /mnt/tmp/
# umount /mnt/tmp
# rmdir /mnt/tmp
# mount -o loop,noexec,nosuid /tmp.img /tmp
# chmod 777 /tmp
# chmod +t /tmp

Now we can add the image to our /etc/fstab to ensure that it automounts on boot:

/tmp.img                /tmp            ext3            loop,nosuid,noexec      1 2

Mass Virtual Hosting Part Seven: Securing PHP with Suhosin

karma

Suhosin is a Korean word which, roughly translated, means guardian angel. It is also the name of a clever PHP extension brought to us by the Hardened-PHP Project. When I found out it could provide transparent on-the-fly session and cookie encryption I thought sexual thoughts and sped off to install it. Fortunately for gentoo users, it's a simple matter of adding the suhosin USE flag to PHP and (re)compiling:

# echo "dev-lang/php suhosin" >> /etc/portage/package.use
# emerge --newuse php

In fact, now is probably a good time to reevaluate your PHP USE flags; I tend to disable a bunch of functions that would simply not exist if certain extensions were not compiled in at all. This is what your stripped-down flags might look like:

[ebuild R ] dev-lang/php-5.2.13 USE="apache2 bcmath berkdb bzip2 cgi cli crypt ctype exif filter gd hash iconv ipv6 json mysql mysqli ncurses nls pcntl pcre pdo readline reflection session simplexml snmp spl ssl suhosin threads truetype unicode xml zip zlib -adabas -birdstep -calendar -cdb -cjk -concurrentmodphp -curl -curlwrappers -db2 -dbase -dbmaker -debug -discard-path -doc -empress -empress-bcs -esoob -fastbuild -fdftk -firebird -flatfile -force-cgi-redirect -frontbase -ftp -gd-external -gdbm -gmp -imap -inifile -interbase -iodbc (-java-external) -kerberos -kolab -ldap -ldap-sasl -libedit -mcve -mhash -msql -mssql -oci8 -oci8-instant-client -odbc -pic -posix -postgres -qdbm -recode -sapdb -sharedext -sharedmem -soap -sockets -solid -spell -sqlite -sybase -sybase-ct -sysvipc -tidy -tokenizer -wddx -xmlreader -xmlrpc -xmlwriter -xpm -xsl -yaz"

Once you've recompiled you're going to have to add the Suhosin configuration directives to your php.ini. For apache open /etc/php/apache-php5/php.ini and tack this on to the end:

; Logging
suhosin.log.syslog = S_ALL
suhosin.log.syslog.facility = LOG_USER
suhosin.log.syslog.priority = LOG_WARNING
suhosin.log.sapi = S_ALL
;suhosin.log.script
;suhosin.log.phpscript
;suhosin.log.script.name
;suhosin.log.phpscript.name
;suhosin.log.use-x-forwarded-for

; Executor Options
suhosin.executor.max_depth = 1000000
suhosin.executor.include.max_traversal = 3
;suhosin.executor.include.whitelist
;suhosin.executor.include.blacklist
;suhosin.executor.func.whitelist
suhosin.executor.func.blacklist = ""
;suhosin.executor.eval.whitelist
suhosin.executor.eval.blacklist = ""
suhosin.executor.disable_eval = On
suhosin.executor.disable_emodifier = On
suhosin.executor.allow_symlink = Off

; Misc Options
suhosin.simulation = Off
suhosin.apc_bug_workaround = Off
suhosin.sql.bailout_on_error = On
;suhosin.sql.user_prefix
;suhosin.sql.user_postfix
suhosin.multiheader = On
suhosin.mail.protect = 2
;suhosin.memory_limit

; Transparent Encryption Options
suhosin.session.encrypt = On
suhosin.session.cryptkey = "INSERT RANDOM CRAP HERE"
suhosin.session.cryptua = On
suhosin.session.cryptdocroot = On
suhosin.session.cryptraddr = 2
suhosin.session.checkraddr = 2
suhosin.cookie.encrypt = On
suhosin.cookie.cryptkey = "INSERT RANDOM CRAP HERE"
suhosin.cookie.cryptua = On
suhosin.cookie.cryptdocroot = On
suhosin.cookie.cryptraddr = 2
suhosin.cookie.checkraddr = 2
;suhosin.cookie.cryptlist
;suhosin.cookie.plainlist

; Filtering Options
suhosin.filter.action = 402
suhosin.cookie.max_array_depth = 100
suhosin.cookie.max_array_index_length = 64
suhosin.cookie.max_name_length = 64
suhosin.cookie.max_totalname_length = 256
suhosin.cookie.max_value_length = 10000
suhosin.cookie.max_vars = 100
suhosin.cookie.disallow_nul = On
suhosin.get.max_array_depth = 50
suhosin.get.max_array_index_length = 64
suhosin.get.max_name_length = 64
suhosin.get.max_totalname_length = 256
suhosin.get.max_value_length = 512
suhosin.get.max_vars = 100
suhosin.get.disallow_nul = On
suhosin.post.max_array_depth = 100
suhosin.post.max_array_index_length = 64
suhosin.post.max_name_length = 64
suhosin.post.max_totalname_length = 256
suhosin.post.max_value_length = 50000000
suhosin.post.max_vars = 200
suhosin.post.disallow_nul = On
suhosin.request.max_array_depth = 100
suhosin.request.max_array_index_length = 64
suhosin.request.max_totalname_length = 256
suhosin.request.max_value_length = 65000
suhosin.request.max_vars = 200
suhosin.request.max_varname_length = 64
suhosin.request.disallow_nul = On
suhosin.upload.max_uploads = 25
suhosin.upload.disallow_elf = On
suhosin.upload.disallow_binary = Off
suhosin.upload.remove_binary = Off
;suhosin.upload.verification_script
suhosin.session.max_id_length = 128

Now head over to http://www.hardened-php.net/suhosin/configuration.html and tailor the configuration to suit your environment. Don't forget to blacklist dangerous functions like apache_child_terminate, apache_setenv, define_syslog_variables, eval, exec, ftp_connect, ftp_exec, ftp_get, ftp_login, ftp_nb_fput, ftp_put, ftp_raw, ftp_rawlist, highlight_file, ini_alter, ini_get_all, ini_restore, inject_code, openlog, passthru, php_uname, phpAds_remoteInfo, phpAds_XmlRpc, phpAds_xmlrpcDecode, phpAds_xmlrpcEncode, popen, posix_getpwuid, posix_kill, posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_setuid, posix_uname, proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, shell_exec, syslog, system, xmlrpc_entity_decode, exec, pipe, set_time_limit, popen, proc_open, parse_ini_file, show_source, mail, dl, ini_set, ini_alter, virtual, openlog, apc_add, apc_bin_dump, apc_bin_dumpfile, apc_bin_loadfile, apc_cache_info, apc_cas, apc_clear_cache, apc_compile_file, apc_dec, apc_define_constants, apc_delete_file, apc_delete, apc_exists, apc_fetch, apc_inc, apc_load_constants, apc_store, symlink and eval!

It seems Suhosin provides its own stack-smashing protection, potentially removing the need for the slow PIC (-fstack_protector_all) compile-time option. Until I get confirmation on this I'll be using both just to be safe.

Mass Virtual Hosting Part Four: MySQL and phpMyAdmin

karma

MySQL is an extremely popular, open-source relational database management system. Oracle acquired the RDBMS some years ago and releases value-added editions and tools to turn a profit. MySQL is fairly ubiquitous in the web hosting market, most PHP-driven applications having been designed primarily or exclusively for it. For this reason it is a good choice when implementing your mass virtual hosting platform as it is what the vast majority of users will expect.

phpMyAdmin is a fantastic PHP-based web application that allows for the easy management of MySQL databases and features through a user-friendly interface. It shares similar ubiquity in the hosting market, having virtually no competitors save custom solutions. Accordingly, phpMyAdmin has evolved to become very ISP/Webhost friendly in that many features important to security and integration are highly configurable.

As noted in an addendum to Part One of this series on Mass Virtual Hosting: Database-backed User Accounts and Authentication I had difficulties running mysqld on machines configured to use libnss-mysql, therefore for the purposes of this article we shall assume that we are working with a stand-alone SQL server and a stand-alone web server. I recommend this configuration whenever resources permit regardless of your choice in authentication schemes, as partitioning important services helps keep either one from overloading the other under stressful or unusual circumstances. As we'll cover in the next part of this series, mysql-proxy can be used to give users the illusion of mysqld running on localhost while the actual server can be located (or relocated) anywhere transparently. This has the additional benefits of preventing information about the private network's topology from leaking and opens the potential for load-balancing and most importantly: automatic fail over.

MySQL generally keeps its configuration files in the /etc/my directory. This is the default MySQL configuration file that comes on a Gentoo installation:

# cat /etc/mysql/my.cnf
# /etc/mysql/my.cnf: The global mysql configuration file.
# $Header: /var/cvsroot/gentoo-x86/dev-db/mysql/files/my.cnf-4.1,v 1.4 2008/11/14 02:16:25 robbat2 Exp $

# The following options will be passed to all MySQL clients
[client]
#password                                       = your_password
port                                            = 3306
socket                                          = /var/run/mysqld/mysqld.sock

[mysql]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                  

[mysqladmin]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                  

[mysqlcheck]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                  

[mysqldump]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                  

[mysqlimport]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                                                                                                                                                          

[mysqlshow]
character-sets-dir=/usr/share/mysql/charsets
default-character-set=utf8                                                                                                                                                          

[myisamchk]
character-sets-dir=/usr/share/mysql/charsets

[myisampack]
character-sets-dir=/usr/share/mysql/charsets

# use [safe_mysqld] with mysql-3
[mysqld_safe]
err-log                                         = /var/log/mysql/mysql.err

# add a section [mysqld-4.1] or [mysqld-5.0] for specific configurations
[mysqld]
character-set-server            = utf8
default-character-set           = utf8
user                                            = mysql
port                                            = 3306
socket                                          = /var/run/mysqld/mysqld.sock
pid-file                                        = /var/run/mysqld/mysqld.pid
log-error                                       = /var/log/mysql/mysqld.err
basedir                                         = /usr
datadir                                         = /var/lib/mysql
skip-locking
key_buffer                                      = 16M
max_allowed_packet                      = 1M
table_cache                             = 64
sort_buffer_size                        = 512K
net_buffer_length                       = 8K
read_buffer_size                        = 256K
read_rnd_buffer_size            = 512K
myisam_sort_buffer_size         = 8M
language                                        = /usr/share/mysql/english   

# security:
# using "localhost" in connects uses sockets by default
# skip-networking
bind-address                            = 0.0.0.0      

log-bin
server-id                                       = 1

# point the following paths to different dedicated disks
tmpdir                                          = /tmp/
#log-update                             = /path-to-dedicated-directory/hostname

# you need the debug USE flag enabled to use the following directives,
# if needed, uncomment them, start the server and issue
# #tail -f /tmp/mysqld.sql /tmp/mysqld.trace
# this will show you *exactly* what's happening in your server ;)     

#log                                            = /tmp/mysqld.sql
#gdb
#debug                                          = d:t:i:o,/tmp/mysqld.trace
#one-thread                                                                

# uncomment the following directives if you are using BDB tables
#bdb_cache_size                         = 4M
#bdb_max_lock                           = 10000                 

# the following is the InnoDB configuration
# if you wish to disable innodb instead
# uncomment just the next line
#skip-innodb
#
# the rest of the innodb config follows:
# don't eat too much memory, we're trying to be safe on 64Mb boxes
# you might want to bump this up a bit on boxes with more RAM
innodb_buffer_pool_size = 16M
# this is the default, increase it if you have lots of tables
innodb_additional_mem_pool_size = 2M
#
# i'd like to use /var/lib/mysql/innodb, but that is seen as a database :-(
# and upstream wants things to be under /var/lib/mysql/, so that's the route
# we have to take for the moment
#innodb_data_home_dir           = /var/lib/mysql/
#innodb_log_arch_dir            = /var/lib/mysql/
#innodb_log_group_home_dir      = /var/lib/mysql/
# you may wish to change this size to be more suitable for your system
# the max is there to avoid run-away growth on your machine
innodb_data_file_path = ibdata1:10M:autoextend:max:128M
# we keep this at around 25% of of innodb_buffer_pool_size
# sensible values range from 1MB to (1/innodb_log_files_in_group*innodb_buffer_pool_size)
innodb_log_file_size = 5M
# this is the default, increase it if you have very large transactions going on
innodb_log_buffer_size = 8M
# this is the default and won't hurt you
# you shouldn't need to tweak it
set-variable = innodb_log_files_in_group=2
# see the innodb config docs, the other options are not always safe
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
innodb_file_per_table

[mysqldump]
quick
max_allowed_packet                      = 16M

[mysql]
# uncomment the next directive if you are not familiar with SQL
#safe-updates

[isamchk]
key_buffer                                      = 20M
sort_buffer_size                        = 20M
read_buffer                             = 2M
write_buffer                            = 2M

[myisamchk]
key_buffer                                      = 20M
sort_buffer_size                        = 20M
read_buffer                             = 2M
write_buffer                            = 2M

[mysqlhotcopy]
interactive-timeout

A lot of tweaking can be done here but it exceeds the scope of this article and shall be the topic for another. Note I have set the bind-address variable to 0.0.0.0, this will allow MySQL to bind to any configured IP address on its host. The default is localhost, which restricts access to sockets connections. On a host with multiple IP addresses it may be mildy beneficial to security to bind mysqld to a specific IP address. I strongly recommend only allowing access to the server from the private network, the less services - particularly critical services that contain valuable data like databases - exposed to the wild the better in all cases. A thoughtfully configured phpMyAdmin installation is truly enough for any administrative task, there is most likely no good reason to give your clients remote MySQL access at all. If you do, make absolutely certain to take advantage of MySQL's SSL capabilities and enforce some kind of brute force protection, such as can be configured using fail2ban.

Since phpMyAdmin will be a conduit for MySQL passwords, site user tables and other sensitive data, it is absolutely imperative that it be served over SSL/TLS. Install it somewhere that makes sense, perhaps an SSL-protected subdomain like sql. or phpmyadmin.yourmanagementdomain.com. If you only want to shell out for a regular single-host certificate a subdirectory of yourmanagementdomain.com like /sql/ will do. Download and extract the latest version of phpMyAdmin to the desired location and create then chmod 777 the temp/ directory. Navigate to the target location and append the /setup/ path, i.e.: http://sql.yourmanagementdomain.com/setup

If you get a blank page, go into your php.ini and enable error reporting. If PHP spits out an error shove it into google, I once had an obscure problem with the filter_var() function because I was missing filter support in my USE flags when it was compiled. Assuming all went well you can now go through the step-by-step configuration wizard; pay attention to detail and make sure the options are suited to your particular environment.

Consider if you really need SSL support when connecting to the server from phpMyAdmin; encryption consumes resources and increases latency but offers protection in environments prone to packet sniffing. In a switched and bridged Layer 2 network of real servers or virtual machines chances are good that if an intruder can sniff your SQL packets they already have your web server or sql server compromised and either is just as bad as the other. Or, unique to virtual machines, they have access to one or more privileged guests  -in which case your problems are much, much bigger.

Compression is also probably more resources than it's worth on a local network, but can be quite beneficial when the SQL server is in a remote location relative to the web server. Ideally we'd like Single-Sign-On with phpMyAdmin so users can access it by loging in to our custom web management software and not need to be prompted for their SQL password. We're going to get to that later in the article, first we want to be able to log in and lock down features so we'll go with cookie-based authentication rather than the signon option. Cookie-based authentication used the username and password of the user actually stored in MySQL, and we should already have our root account configured.

Under the Security tab there is a Force SSL Connection option. You are strongly encouraged to enable it, it will automatically redirect anyone attempting to access phpMyAdmin from an unsecured connection to a secure one. phpMyAdmin can use its own database to provide additional functionality; create a database and a user for phpMyAdmin in MySQL:

create database phpmyadmin;
grant all privileges on phpmyadmin.* to phpmyadmin@'%' identified by 'pass';

The table structure is located in /scripts/create_tables.sql, install it from the web server like so:

# mysql -hsql-server -uphpmyadmin -ppass phpmyadmin < scripts/create_tables.sql

Once you're finished with the wizard save the file and copy config.inc.php from the /config/ directory. You may now delete the /config/ and /setup/ directories.

I like to remove some of the tabs phpMyAdmin comes with since they can pose a security risk or confuse users with needless information. Edit /libraries/server_links.php and comment-out the arrays for the tabs you would like to remove. Bear in mind that these changes will not stick after an upgrade.

We don't want to allow root to log in from anywhere but the SQL server itself so create a database and user to test logging in with:

create database testdb;
grant all privileges on testdb.* to test@'%' identified by 'testpass';

Make sure your test account can be accessed from the address of the server offering phpMyAdmin. Use the SQL wildcard (%) character to allow access from any location. Now try logging in through phpMyAdmin with the test account.

phpMyAdmin comes with a sample single-sign-on script example in its scripts/ directory:

<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
 * Single signon for phpMyAdmin
 *
 * This is just example how to use single signon with phpMyAdmin, it is
 * not intended to be perfect code and look, only shows how you can
 * integrate this functionality in your application.
 *
 * @version $Id$
 * @package phpMyAdmin
 * @subpackage Example
 */

/* Was data posted? */
if (isset($_POST['user'])) {
    /* Need to have cookie visible from parent directory */
    session_set_cookie_params(0, '/', '', 0);
    /* Create signon session */
    $session_name = 'SignonSession';
    session_name($session_name);
    session_start();
    /* Store there credentials */
    $_SESSION['PMA_single_signon_user'] = $_POST['user'];
    $_SESSION['PMA_single_signon_password'] = $_POST['password'];
    $_SESSION['PMA_single_signon_host'] = $_POST['host'];
    $_SESSION['PMA_single_signon_port'] = $_POST['port'];
    $id = session_id();
    /* Close that session */
    session_write_close();
    /* Redirect to phpMyAdmin (should use absolute URL here!) */
    header('Location: ../index.php');
} else {
    /* Show simple form */
    header('Content-Type: text/html; charset=utf-8');
    echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
    ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
    <link rel="icon" href="../favicon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
    <title>phpMyAdmin single signon example</title>
</head>
<body>
<form action="signon.php" method="post">
Username: <input type="text" name="user" /><br />
Password: <input type="password" name="password" /><br />
Host: (will use the one from config.inc.php by default) <input type="text" name="host" /><br />
Port: (will use the one from config.inc.php by default) <input type="text" name="port" /><br />
<input type="submit" />
</form>
</body>
</html>
<?php
}
?>

As you can see you will need to have your users' passwords stored somewhere in cleartext. By popping the session code into your administration front-end (somewhere before headers are sent). I suggest that a user must click a button or take some other obvious action to activate the session rather than starting it as soon as they log into your interface, there is no sense in bestowing access where none is needed.

Open the phpMyAdmin config file and edit it to reflect:

$cfg['Servers'][$i]['auth_type'] = 'signon';
$cfg['Servers'][$i]['SignonSession'] = "SignonSession";
$cfg['Servers'][$i]['SignonURL'] = "https://yourdomain.com/login-page";
$cfg['Servers'][$i]['LogoutURL'] = "https://yourdomain.com/logout-page";
<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* Single signon for phpMyAdmin
*
* This is just example how to use single signon with phpMyAdmin, it is
* not intended to be perfect code and look, only shows how you can
* integrate this functionality in your application.
*
* @version $Id$
* @package phpMyAdmin
* @subpackage Example
*/

/* Was data posted? */
if (isset($_POST['user'])) {
/* Need to have cookie visible from parent directory */
session_set_cookie_params(0, '/', '', 0);
/* Create signon session */
$session_name = 'SignonSession';
session_name($session_name);
session_start();
/* Store there credentials */
$_SESSION['PMA_single_signon_user'] = $_POST['user'];
$_SESSION['PMA_single_signon_password'] = $_POST['password'];
$_SESSION['PMA_single_signon_host'] = $_POST['host'];
$_SESSION['PMA_single_signon_port'] = $_POST['port'];
$id = session_id();
/* Close that session */
session_write_close();
/* Redirect to phpMyAdmin (should use absolute URL here!) */
header('Location: ../index.php');
} else {
/* Show simple form */
header('Content-Type: text/html; charset=utf-8');
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<link rel="icon" href="../favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
<title>phpMyAdmin single signon example</title>
</head>
<body>
<form action="signon.php" method="post">
Username: <input type="text" name="user" /><br />
Password: <input type="password" name="password" /><br />
Host: (will use the one from config.inc.php by default) <input type="text" name="host" /><br />
Port: (will use the one from config.inc.php by default) <input type="text" name="port" /><br />
<input type="submit" />
</form>
</body>
</html>
<?php
}
?>

Mass Virtual Hosting Part Six: (Remote) Apache Vhost Configuration and Privileged Command Execution with Database-Backed Upkeep System

karma

What a mouthful.

The more I worked with mod_vdbh the more I realized it's not for me. Neither is its more advanced fork, mod_vhs which adds some useful extensions. While database-backed configuration is an elegant solution to mass virtual hosting it falls short in that it lacks these key (for my purposes) abilities or requires hackery that violates the Keep It Simple, Stupid rule

  • Apache configuration can not be customized on a per-vhost basis, requiring the use of generated .htaccess files for tricks that might be implemented in a user-friendly way via a front-end, such as custom error pages.
  • Additional Directory directives (i.e. those that don't match the wildcard directives for whatever reason) need to be put into flat files anyway.
  • Logs for individual sites must be split from a common log by a resource-consuming external solution
  • ScriptAlias directive (mod_vhs) only works for one global directory for all sites or not at all (mod_vdbh)
  • These modules are unmaintained, if something better comes out there is the whole hassle of migration to contend with.
  • A new version of apache may break old modules, but flat files will always be supported.

This is a serious problem if, like me, you are used to setting up vhost accounts in such a fashion:

/home/user/website.com/htdocs
/home/user/website.com/log
/home/user/website.com/cgi-bin

How can using flat files for mass virtual hosting be as easy to manage as databases one might ask? The answer is simple: generate the flat files from data stored in a database.

My configuration front-end sits on a web server that is independent from the one that serves the 'public' virtual hosts. This necessitated the ability to execute commands remotely as root, such as creating the directories for a new host, while at the same time taking into consideration the security implications of average users being able to pass data to the server as root.

My solution came in the form of a two-part system; a shared database that is used to pass sanity-checked data from the configuration interface and an administrative upkeep script run as root by cron every 5 minutes on the virtual hosting server. The script executes appropriate commands with the data set provided then flags its tasks as completed in the database. By storing arrays of raw data and indicating the job type one can avoid altogether the inherent problem of sending straight-up command line commands to the remote server. Careful variable checking in both the configuration interface where the data is added, then in the upkeep script where the tasks are then run can result in rock solid security, despite the fact that we are talking about translating a user's actions on a web page to root-privileged commands.

In my frustration with the database modules for Apache I realized that the same system could be adapted to write, delete and overwrite individual apache configuration files based on back-end information. It helps to think of the situation like when one uses memcache or APC variable caching with an SQL database; the actual driving force behind the application is the database information but as it is pulled into memory the cache sits between them. By simply dropping the files into a directory and using a wildcard with an Include directive all it takes is reloading apache once updates have been performed for changes to take effect. Through maintaining a master database from which the files are generated one could easily delete the entire directory, run a regeneration script and they would all reappear.

It's at this point one may find one's head shaking: one of the prime benefits of database-backed configuration is that the apache server does not have to be restarted when a new vhost is added. Indeed, it is the very reason most people seem to switch to the solution. However I didn't say restart, I said reload - it seems a lot of people have overlooked apache's ability to reload configuration files gracefully - that is without being restarted all at once and without dropping open connections. Fortunately, I have not.

One of the major benefits of this approach weighed against database-only configuration is once apache has been reloaded all of the configuration is loaded into RAM; there is no need to worry about thread safety or hundreds of redundant connections and queries to your database server - a problem that becomes worse as your platform scales up without the aide of some sort of abstraction layer like pooled connections (mod_dbm support currently still in development (or not) for mod_vhs) or caching as could be implemented with much hackery and mysql-proxy.

This article will show you - in much simpler and specific detail - how I have implemented what I call an upkeep system that can manage virtual hosts and run privileged commands passed to it (from Joe User in his web browser) safely on a local or remote server with nothing more than PHP and MySQL. It is not at all hard to imagine this system being adapted to manage multiple servers, the workload distributed or logically divided among them given some automated mechanism or instruction from the configuration interface.

Bear in mind that it's not the norm to use PHP for server-side-only scripts and you may wish to implement the idea in PERL or Python or something more traditional - but the application is sound, I'm good at PHP and I am secure in my masculinity :) PHP should be available on the web servers we intend to manage anyway, but may be more trouble than it's worth to install it on, say, an NFS-only server if you want to split up file-related commands from configuration tasks.

The other drawback to this approach is the 5-minute delay between runs of the upkeep script. To address this I simply add a notification event to the database which the configuration interface searches for and reports to the user if they have any tasks pending completion.

First, create the shared table on a mutually-accessible SQL server. Remember to use one account for administration and one for the upkeep script, applying permissions frugally.

CREATE TABLE IF NOT EXISTS `upkeep` (
 `id` bigint(20) NOT NULL auto_increment,
 `uid` int(11) NOT NULL default '0',
 `date` int(11) NOT NULL default '0',
 `completed` int(11) NOT NULL default '0',
 `status` varchar(30) NOT NULL,
 `type` varchar(30) NOT NULL,
 `data` longtext NOT NULL,
 PRIMARY KEY  (`id`)
)

And we need one for the virtual hosts:

CREATE TABLE IF NOT EXISTS `virtual_hosts` (
 `id` int(11) NOT NULL auto_increment,
 `date` int(11) NOT NULL,
 `user` varchar(255) NOT NULL,
 `yuid` int(11) NOT NULL,
 `appid` int(11) NOT NULL,
 `server` char(255) NOT NULL,
 `environment_variable` char(255) default NULL,
 `subof` int(11) NOT NULL,
 `firewall` longtext NOT NULL,
 `errorpages` longtext NOT NULL,
 PRIMARY KEY  (`id`),
 KEY `yuid` (`yuid`),
 KEY `server` (`server`)
);

I'm using the uid field in the first table and the yuid field in the second to store the administrative interface's account ID number or the person who uses the functions. Next we're going to need some functions for the administration front-end to interface with:

function hostedUPKEEP($type, $data, $uid=0)
{
 if(is_array($data))
 $data = serialize($data);

 $data = mysql_real_escape_string($data);

 mysql_query("insert into `upkeep` (`uid`, `date`, `status`, `type`, `data`) values ('$uid', '".time()."', 'pending', '$type', '$data')");
}

function hostedUPKEEPMkdir($user, $group, $path)
{
 $data['user'] = $user;
 $data['group'] = $group;
 $data['path'] = $path;

 hostedUPKEEP('mkdir', $data);
}

function hostedVHOSTAddHost($user, $yuid, $appid, $server)
{
 $firewall = $errorpages = serialize(array());
 $date = time();

 mysql_query("insert into `virtual_hosts` (`user`, `date`, `yuid`, `appid`, `server`, `firewall`, `errorpages`) values ('$user', '$date', '$yuid', '$appid', '$server', '$firewall', '$errorpages')");
 $idgrabr = mysql_query("select * from ``virtual_hosts` where `user` = '$user' and `server` = '$server'");
 $idgrabo = mysql_fetch_object($idgrabr);

 hostedUPKEEPMkdir('root', 'root', "/home/$user");
 hostedUPKEEPMkdir($user, 'hosted', "/home/$user/$server");
 hostedUPKEEPMkdir($user, 'hosted', "/home/$user/$server/htdocs");
 hostedUPKEEPMkdir($user, 'hosted', "/home/$user/$server/log");
 hostedUPKEEPMkdir($user, 'hosted', "/home/$user/$server/cgi-bin");
 hostedUPKEEP('vhost', $idgrabo->id);
 hostedUPKEEP('notification', 'jobs pending notification', $yuid);
}

Since staff may edit user settings I pass the affected user's front-end UID to a notification event, the user's front-end will look for events marked pending with their UID and report that they must wait a little while for changes to take effect. Before passing any data to these functions it is important that you make sure it has been as carefully sanitized as possible. The following is a simple upkeep script that you can drop into /sbin/, chown root: and chmod 700 then add to cron at your preferred interval:

#!/usr/bin/php
<?php

$sql_host = '';
$sql_user = '';
$sql_pass = '';
$sql_base = '';

$sql_h = mysql_pconnect($sql_host, $sql_user, $sql_pass);
$sql_d = mysql_select_db($sql_base, $sql_h);

$forbidden_users = array('www',
'ftp',
'sql',
'mysql',
'database',
'db',
'sftp',
'ftps',
'sync',                                                                                                                          
'shutdown',                                                                                                                
'halt',                                                                                   
'mail',                                                                                                                 
'news',
'uucp',
'operator',
'calendar',
'docs',
'man',
'postmaster',
'cron',
'ftp',
'sshd',
'ssh',
'at',
'squid',
'gdm',
'xfs',
'games',
'named',
'postgres',
'apache',
'admin',
'administrator',
'cyrus',
'vpopmail',
'alias',
'qmaild',
'qmaill',
'qmailp',
'qmailq',
'qmailr',
'qmails',
'postfix',
'smmsp',
'portage',
'guest',
'nobody',
'clamav',
'amavis',
'vmail',
'ntp',
'deleted',
'mrtg',
'sockd',
'lighttpd',
'memcached',
'smokeping',
'rpc',
'anon',
'site',
'sites',
'anonymous',
'pop',
'pop3',
'smtp',
'sendmail',
'information_schema',
'test');

function checkname($string)
{
 global $forbidden_users;
 foreach($forbidden_users as $fuse)
 {
 if($fuse == strtolower($string))
 die("Forbidden User");
 }
}

$result = mysql_query("select * from `upkeep` where `status` = 'pending' order by `date` asc");
while($object = mysql_fetch_object($result))
{
 if($object->type == 'mkdir')
 {
 $data = unserialize($object->data);
 checkname($data['user']);
 checkname($data['group']);
 exec("mkdir -p ".escapeshellcmd($data['path']));
 exec("chown ".escapeshellcmd(ereg_replace("[^A-Za-z0-9]", '', $data['user'])).":".escapeshellcmd(ereg_replace("[^A-Za-z0-9]", '', $data['group']))." ".escapeshellcmd($data['path']));
 }

 if($object->type == 'vhost')
 {
 $id = $object->data;
 $vhost_result = mysql_query("select * from `virtual_hosts` where `id` = '$id'");
 $vhost_object = mysql_fetch_object($vhost_result);

 $domain = $vhost_object->server;
 $user = ereg_replace("[^A-Za-z0-9]", '', $vhost_object->user);

 checkname($user);

 $errarray = unserialize($vhost_object->errarray);
 if(!empty($errarray[0]))
 {
 $errorpages = '';
 foreach($errarray as $code => $loc)
 {
 $errorpages .= "\n\tErrorDocument $code $loc";
 }
 }
 else
 {
 $errorpages = '';
 }

 $fwarray = unserialize($vhost_object->fwrarray);
 if(!empty($fwarray[0]))
 {
 $firewall = '';
 foreach($fwarray as $address)
 {
 $firewall .= "\n\tDeny from $address";
 }
 }
 else
 {
 $firewall = '';
 }

 $domainesc = str_replace('.', '\.', $domain);

 $file = "<VirtualHost *:80>
\tServerName $domain
\tServerAlias www.$domain
\tDocumentRoot /home/$user/$domain/htdocs
\tScriptAlias /cgi-bin/ /home/$user/$domain/cgi-bin/
\tErrorLog /home/$user/$domain/log/error_log
\tTransferLog /home/$user/$domain/log/access_log{$errorpages}
\t<IfModule mod_rewrite.c>
\t\tRewriteEngine on
\t\tRewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$
\t\tRewriteRule .* - [F]
\t\tRewriteCond %{HTTP_HOST} ^www\.$domainesc$ [NC]
\t\tRewriteRule ^(.*)\$ http://$domainesc/\$1 [R=307,L]
\t</IfModule>
\t<IfModule mod_access.c>
\t\tOrder Allow,Deny
\t\tAllow from all{$firewall}
\t</IfModule>
</VirtualHost>";

 $fh = fopen("/etc/apache2/hosted.d/{$user}_{$domain}.conf", 'w');
 fwrite($fh, $file);
 fclose($fh);

 exec("/etc/init.d/apache2 reload");        // Change the path to your apache2ctl if the init script does not support reload.
 }

 mysql_query("update `upkeep` set `status` = 'completed', `completed` = '".time()."' where `id` = '{$object->id}'");
}

?>

Now create the directory /etc/apache2/hosted.d (or whatever you prefer) and add this directive to the end of your httpd.conf:

Include /etc/apache2/hosted.d/*.conf