Monday, June 1, 2009

Episode #43: Users & Groups

Ed rushes in:

Here's an easy one that I use all the time when analyzing a system. When auditing a box or investigating a compromised system, I often want to double check which groups are defined locally, along with the membership of each group. I especially focus on who is in the admin group. We can dump a list of groups as follows:

C:\> net localgroup

Then, we can check the accounts associated with each group using:

C:\> net localgroup [groupname]

That's all well and good, but how can we get all of this information at one time? We could use a FOR /F loop to iterate on the output of our first command (group names) showing the membership (user names). Put it all together, and you have:

C:\> for /F "skip=4 delims=*" %i in ('net localgroup ^| find /v
"The command completed successfully"') do @net localgroup "%i"

Here, I'm using a for /F loop to parse through the output of "net localgroup". I filter (find) through the output of that command to choose lines that do not have (/v) the annoying "The command completed successfully" output. Otherwise, I'd get errors. I define some custom parsing in my FOR /F loop to skip the first 4 lines of cruft, and set a delimeter of * to remove that garbage that Microsoft prepends to each group name. BTW, what's with Microsoft making the output of their commands so ugly? Why do we have to parse all this garbage? How about they make the output useful as is? Oh well... Anyway, once I've parsed the output of "net localgroup" to get a group list, I push the output through "net localgroup" again to get a list of members.

Hal's going off the deep end:

Ed's challenge seemed deceptively simple when I first read it. Then my brain kicked in, and as usual that made things infinitely more difficult. At first I thought this was going to be as simple as using "cut" to pull the appropriate fields out of /etc/group:

$ cut -f1,4 -d: /etc/group
root:
daemon:
bin:
sys:
adm:hal
[...]

Alternatively, if you only cared about groups that actually had users listed in the last field, you could do:

$ cut -f1,4 -d: /etc/group | grep -v ':$'
adm:hal
dialout:hal
cdrom:hal
audio:pulse
plugdev:hal
lpadmin:hal
admin:hal
sambashare:hal

But now my uppity brain intruded with an ugly fact: by only looking at /etc/group, we're ignoring the users' default group assignments in /etc/passwd. What we really need to do is merge the information in /etc/passwd with the group assignments in /etc/group. I'll warn you up front that my final solution skirts perilously close to the edge of our "no scripting" rule, but here goes.

We're going to be using the "join" command to stitch together the /etc/passwd and /etc/group files on the group ID column. However, "join" requires both of its input files to be sorted on the join field. So before we do anything we need to accomplish this:

$ sort -n -t: -k4 /etc/passwd >passwd.sorted
$ sort -n -t: -k3 /etc/group >group.sorted

In the "sort" commands, "-n" means do a numeric sort, "-t" specifies the field delimiter, and "-k" is used to specify the field(s) to sort on.

Once we have the sorted files, producing the output we want is trivial:

$ join -a 1 -t: -1 3 -2 4 group.sorted passwd.sorted | \
awk -F: '{ grps[$2] = grps[$2] "," $4 "," $5 }
END { for ( g in grps ) print g ":" grps[g] }' | \
sed -r 's/(:|,),*/\1/g; s/,$//' | sort

[...]
list:list
lpadmin:hal
lp:hplip,lp
mail:mail
man:man
messagebus:messagebus
mlocate:
netdev:
news:news
nogroup:nobody,sshd,sync
[...]

While I hate to belabor the obvious, let me go over the above example line-by-line for the two or three folks reading this blog who might be confused:


  • "join" is a bit funky. The "-t" option specifies the column delimiter, just like "sort", and you can probably guess that "-1 3" and "-2 4" are how we're specifying the join column in file 1 ("-1") and file 2 ("-2"). Normally "join" will only output lines when it can find lines in both files that it can merge together. However, the "-a 1" option tells "join" to output all lines from file 1, even if there's no corresponding line in file 2.

    So that you can understand the rest of the command-line above, let me show you some of the output from the "join" command by itself:

    $ join -a 1 -t: -1 3 -2 4 group.sorted passwd.sorted
    [...]
    124:sambashare:x:hal
    125:ntp:x::ntp:x:112::/home/ntp:/bin/false
    126:bind:x::bind:x:113::/var/cache/bind:/bin/false
    1000:hal:x::hal:x:1000:Hal Pomeranz,,,:/home/hal:/bin/bash
    65534:nogroup:x::nobody:x:65534:nobody:/nonexistent:/bin/sh
    65534:nogroup:x::sshd:x:114::/var/run/sshd:/usr/sbin/nologin
    65534:nogroup:x::sync:x:4:sync:/bin:/bin/sync

    When doing its output, "join" puts the merge column value (the GID in our case) up at the front of each line of output. Then you see the remaining fields of the first input file (group name, group password, user list), followed by the remaining fields of the second input file (user name, BSD password, UID, and so on). The "sambashare" line at the top of our sample output is an example of a group that had no corresponding users in /etc/passwd. The "nogroup" lines toward the bottom of the output are an example of a single group that actually has several users associated with it in /etc/passwd.

    Somehow we've got to pull the output from the "join" command into a consolidated output format. That's going to require some pretty flexible text processing, plus the ability to merge user names from multiple lines of output, like the "nogroup" lines in our sample output. Sounds like a job for awk.

  • In the awk expression I'm using "-F:" to tell awk to split the input lines on colons, rather than whitespace which is the default. Now the group name is always in field 2, and the list of users from the /etc/group is in field 4, and the user name from /etc/passwd is in field 5. As I read each line of input, I'm building up an array indexed by group name that contains a list of all the values in fields 4 and 5, separated by commas. In the "END" block that gets processed when the input is exhausted I'm outputting the group name, a colon, and the list of users.

    The only problem is that sometimes field 4 and field 5 are null, so you get some extra commas in the output:

    $ join -a 1 -t: -1 3 -2 4 group.sorted passwd.sorted | \
    awk -F: '{ grps[$2] = grps[$2] "," $4 "," $5 }
    END { for ( g in grps ) print g ":" grps[g] }'

    [...]
    sambashare:,hal,
    nogroup:,,nobody,,sshd,,sync
    [...]

    A little "sed" will clean that right up.

  • Our "sed" expression actually contains two substitution operations separated by a semicolon: "s/(:|,),*/\1/g" and "s/,$//". Both substitutions will be applied to all input lines.

    The first subsitution is the most complex. We're matching either a colon or a comma followed by some number of extra commas and replacing that with the initial colon or comma. This allows us to remove all of the extra commas in the middle of the output lines.

    The second substitution matches commas at the end of the line and removes them (replaces them with nothing).


We throw a final "sort" command at the end of the pipeline so we get the output sorted by group name, but the hard part is basically over.

Clever readers will note that there's a potential problem with my solution. What if the "nogroup" entry in /etc/group had a user list like "nogroup:x:65534:foo,bar"? Because there were multiple /etc/passwd lines that were associated with "nogroup", I'd end up repeating the users in from the list in /etc/group multiple times:

$ join -a 1 -t: -1 3 -2 4 group.sorted passwd.sorted | ... | grep nogroup
nogroup:foo,bar,nobody,foo,bar,sshd,foo,bar,sync

The real solution requires introducing some conditional logic into the middle of the awk expression in order to avoid this duplication:

$ join -a 1 -t: -1 3 -2 4 group.sorted passwd.sorted | \
awk -F: '{ if (grps[$2]) { grps[$2] = grps[$2] "," $5 }
else { grps[$2] = $4 "," $5 } }
END { for ( g in grps ) print g ":" grps[g] }' | \
sed -r 's/(:|,),*/\1/g; s/,$//' | sort

[...]
nogroup:foo,bar,nobody,sshd,sync
[...]

The "if" statement in the middle of the awk code is checking to see whether we've seen this group before or not. The first time we see a group (the "else" clause), we make a new entry in the "grps" array with both the user list from /etc/group ($4) and the user name from the /etc/passwd entry ($5). Otherwise, we just append the user name info from the /etc/passwd entry and don't bother re-appending the group list from /etc/group.

I was able to successfully type the above code into a single command-line, but it's clearly a small script at this point. So I'd say that it at least goes against the spirit of the rules of this blog.

Friday, May 29, 2009

Episode #42: Listing and Dropping SMB Sessions

Ed jumps:

On a Windows machine, sometimes an admin or user needs to see currently open SMB sessions going from or to their box, perhaps being used to mount a remote file share, get remote registry access, or otherwise plunder... ahem... I mean access the system.

To get a list of SMB sessions a Windows machines has opened to destination servers (which could be other Windows boxen or smbd's on Linux or Unix), you could run:

C:\> net use
New connections will be remembered.

Status Local Remote Network

-------------------------------------------------------------------------------
OK Z: \\10.1.1.105\c$ Microsoft Windows Network
The command completed successfully.
That shows you outbound SMB connections, those that your machine is acting as a client on. To flip things around and see who has opened an SMB session with your machine (i.e., to display who your box is acting as an SMB server to right now), you could run:
C:\> net session

Computer User name Client Type Opens Idle time

-------------------------------------------------------------------------------
\\FRED ED Windows 2002 Serv 0 00:00:40

The command completed successfully.
Note that it shows me the client computer name and the user who has made the connection. The client type refers to the operating system that initiated the inbound session (Windows 2002 is how XP is depicted here). We also see idle time.

That's all well and good, but what if you run those commands and notice some evil SMB session either to or from your box? Perhaps there is an SMB session set up by a bad guy or unauthorized user, and you want to kick them out.

If you want to drop sessions from the client-side, you could run:
C:\> net use \\[ServerMachineName] /del
You'll be prompted about whether you really want to drop that connection. When prompted, hit Y and Enter. If you don't want to be prompted, just add a "/y" to the command above.

Or, if you want to blow away all SMB sessions that your client has initiated with server machines out there, you could run:
C:\> net use * /del /y
Now let's move to the server side. This one is important if you are responding to incidents in which a bad guy has opened an SMB session with one of your Windows servers, perhaps across your intranet. Maybe the server is vitally important, and you aren't allowed to pull the plug. Yet, you need to act fast to bump the bad guy off. Many Windows admins know how to do this at the GUI (launch compmgmt.msc, go to System Tools-->Shared Folders-->Sessions. Right click on evil session and select "Close session"). But, I find disconnecting SMB connections from the server-side much easier to do on the command line with:
C:\> net session \\[ClientMachineName] /del
That'll drop that pesky user and session, and keep your box running. You may want to disable that user account the bad guy relied on via the "net user [AccountName] active:no" command, as mentioned in Episode #34: Suspicious Password Entries.

It's interesting to notice the lack of symmetry with disconnecting client versus server SMB sessions. Dropping connections to servers with "net use" supports the * wildcard above, and supports the /y option to suppress the "Do you want to continue..." prompt. Dropping connections from clients supports neither the * nor does it prompt you to verify that you want to drop them.

Hal retorts:

Assuming your Unix/Linux distro has the smbfs tools from the Samba project installed, mounting and unmounting Windows shares from a client is pretty straightforward. You can either use the "smbmount" command or just "mount -t cifs ..." as root:

# mount -t cifs //server/hal /mnt -o user=hal,uid=hal,gid=hal   # mount and map ownerships
# umount /mnt # unmount file system

The "mount" command will prompt you to enter the password for the specified "user=" and then map all the owner/group owner settings on files based on the specified "uid="/"gid=" options.

Figuring out what Windows shares your client has mounted is straightforward too. You can use either "mount" or "df" (and you don't need to be root here):

$ mount -t cifs
//server/hal on /mnt type cifs (rw,mand)
$ df -t cifs
Filesystem 1K-blocks Used Available Use% Mounted on
//server/hal 627661376 146659564 448604092 25% /mnt

The only caveat here is that the user GUI may provide an alternate method for mounting Windows shares that may make it more difficult to figure out all of the file systems a given system has mounted. For example, when I mount Windows shares via the Gnome-based GUI on my Ubuntu system, it uses GVFS to handle the mount. There's really very little helpful information you can get out of GVFS on the command-line:

$ mount -t fuse.gvfs-fuse-daemon
gvfs-fuse-daemon on /home/hal/.gvfs type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=hal)
$ df -t fuse.gvfs-fuse-daemon
df: no file systems processed

The "mount" command tells me where the share is mounted, but not where it's mounted from. "df" has no clue at all. I hate GVFS.

So it may be more productive to interrogate your Samba server about what clients are currently accessing shares. You can use the "smbstatus" command on your Samba server host for this. What's interesting is that you don't have to be root to use "smbstatus". I'm not entirely certain that's a good thing, since it gives you information about other users' shares in addition to your own:

$ smbstatus 
Samba version 3.0.33-3.7.el5
PID Username Group Machine
-------------------------------------------------------------------
32752 hal hal elk (192.168.4.1)
32733 hal hal elk (192.168.4.1)
5320 laura laura wapiti (192.168.4.2)

Service pid machine Connected at
-------------------------------------------------------
hal 32733 elk Tue May 26 14:57:15 2009
laura 5320 wapiti Tue May 12 11:33:32 2009
iTunes 5320 wapiti Tue May 12 11:33:29 2009
hal 32752 elk Tue May 26 15:02:29 2009

No locked files

You can see I'm mounting my "hal" share twice (once from the command line with "mount -t cifs" and once via GVFS, though you can't tell that from the above output). My wife Laura has got her homedir mounted on her desktop machine, along with her iTunes music folder.

If you have root access, you can use the "smbcontrol" command to forcibly disable currently active shares. You can either disable particular shares by PID (see the "smbstatus" output above) or ruthlessly crush all systems mounting a particular share:

# smbcontrol 32733 close-share hal       # close a single share instance, PID 32733
# smbcontrol smbd close-share hal # nuke all clients mounting "hal"

It should be noted, however, that the disconnected user can simply re-mount the given share at will. So if you really want to keep them off the server you'll need to remove their account (or disable the password) before knocking them off with "smbcontrol".

One other item worth mentioning before I sign off this Episode is that the Samba tools also include a minimal version of the "net" command for your Unix/Linux systems. But many features are missing-- like "net use" for example. So I haven't found the Samba "net" command all that useful in general.

Wednesday, May 27, 2009

Episode #41: DoS or No DoS, That Is the Question

Ed muses:

I was talking with a sysadmin buddy a couple of months ago, who told me he thought his system was under a SYN flood Denial of Service attack, but he wasn't sure. I asked, "Why aren't you sure?" He told me that he couldn't get ahold of his network guys to look at the router and IDS. I said, "You don't need them... just measure it on your end system." "How?" he asked. "Count the number of half-open connections... Oh, and you should count the number of full-open connections too, in case you have a connection flood," I answered. "How?" he repeated.

I told him to use our good friend, netstat. Half-open TCP connections are generated by a SYN flood when an attacker uses a spoofed source address that never sends RESETs to tear down half-open connections. Netstat shows such items in its output as "SYN_RECEIVED". We can count the number of half-open connections using:
C:\> netstat -na | find /c "SYN_RECEIVED"
I'm simply using the /c option of the find command to look for connections in that state. Note that find is case sensitive, so I put in all caps for SYN_RECEIVED. The find command with /i is case insensitive.

Please note that the number of normal half-open connections for most systems is relatively small, typically under a hundred. If you see several hundred, you may have a SYN flood.

Another possibility involves the attacker launching a connection flood, not just a SYN flood. Here, the bad guy won't spoof anything, but will actually complete the three-way handshake with your system again and again. Some bot-net attacks do this by sending HTTP requests to a flood target because it blends in with normal web surfing. We can count those with netstat too, using:
C:\> netstat -na | find /c "ESTABLISHED"
Now, the number of established connections is heavily dependent on the nature and use of your given machine. A busy mail server or web server may have several hundred, or it might not. It all depends. What we need to look for here is a deviation from normal behavior for the system, with a lot more connections that we normall expect.

But, the beauty here is that we are using built-in tools to determine whether we've got a SYN or connection flood, without having to bother the network or IDS guys.

Hal comments:

This is, of course, a lot easier in the Unix shell than in Windows. In fact, I can actually give you counts for all current socket states with a single command line:

$ netstat -an | awk '/^tcp/ {print $6}' | sort | uniq -c     
13 ESTABLISHED
29 LISTEN

Thanks for giving me an easy one, Ed. Maybe I'll do the same for you sometime. Maybe.

Monday, May 25, 2009

Episode #40: Ed's Heresy

Ed opens up by speaking a bit of heresy:

Let me start by saying that I love the command line. I have stated publicly that I find it an incredibly powerful paradigm for interacting with computers. When I really get on a roll, I've even been known to say that the GUI was a mistake that humanity should have avoided.

That said, I'd like to utter a bit of heresy for this here command-line blog. Sometimes... just occasionally... every once and a while... I do something on my Windows box using a GUI. Some of the GUIs just make it easier to get things done. Others are useful so I can check to make sure a change I made at the command line had my desired effect.

So, what does this have to do with command-line kung fu? Well, I launch pretty much every GUI-based tool on my Windows box using the command line. The cmd.exe window is my main user interface, which I periodically use to launch ancillary GUI tools that act as helpers.

You see, launching Windows GUI tools from the command line helps to avoid the constant churn of Microsoft moving things from version to version. Rather than digging through Start-->Programs-->Accessories... or whatever, I just kick off the GUI from my command line.

Truth be told, my work flow is a synthesis of command-line and GUI, with cmd.exe doing about 70% of the work, assorted GUIs doing another 20%, and 10% for VBS or (increasingly) Powershell. I've memorized many of the most useful GUIs that can be launched from the command line. They essentially come in three forms: MSCs (Microsoft Controls), EXEs, and CPLs (Control Panel Tools). Here are my faves, each of which can be launched at the Windows command-line, so you won't have to dig through the Windows GUI ever again:

lusrmgr.msc = Local User Manager
eventvwr.msc = Event Viewer
services.msc = Services Controller
secpol.msc = Security Policy Editor - This one is really useful because it allows you to alter hundreds of registry key equivalents and other settings that would be a pain to do at the command-line.

taskmgr.exe = Task Manager
explorer.exe = Windows Explorer
regedit.exe = Registry Editor
mmc.exe = Generic "empty" Microsoft Management Console, into which I can Add/Remove Snap-ins to manage all kinds of other stuff
msconfig.exe = Microsoft Configuration, including autostart entries and services - Note that this one is not included in cmd.exe's PATH on Windows XP (it is in the PATH on Vista). You can invoke it on XP by running C:\windows\pchealth\helpctr\binaries\msconfig.exe
control.exe = Bring Up the overall Control Panel

wscui.cpl = Windows Security Center control
firewall.cpl = Windows Firewall Config GUI
wuaucpl.cpl
= Windows Automatic Update Configuration

If you'd like to see the other control panel piece parts, you can run:

C:\> dir c:\windows\system32\*.cpl


There are others beyond this list, but these are my trusty aids, extending my GUI. Of all of these, the ones I use most are secpol.msc (because of its access to hundreds of settings), msconfig.exe (as a quick configuration checker), eventvwr.msc (because Windows command-line tools for viewing events are kind of a pain), and good old regedit.exe (makes random adventures in the registry easier than with the reg command).

So, Hal and Paul... are there any GUI-based tools you find yourself launching from the command-line a lot? For Linux, Hal, is there a GUI tool you launch from the command-line because it's just easier to get a given task done in the GUI? And, surely Paul must launch GUIs from the Mac OS X command-line, given the platypus of an operating system he's saddled with. What say you, gentlemen?

Hal confesses:

Wow, I feel like this is an impromptu meeting of "GUI Users Anonymous" or something. As long as we're all testifying, I have to admit that I've always found both printer configuration and Samba configuration to be a huge hassle, and I will often end up using whatever GUIs happen to be available for configuring them.

Often I'll use the GUI to figure out a basic template for the configuration changes I need and then use command-line tools to replicate those configuration templates on other systems. While it's not always clear what configuration files the GUI might be tweaking, remember that you can use the trick from Episode #29 to find them: "touch /tmp/timestamp", make changes via the GUI, and then "find /etc -newer /tmp/timestamp" to find the changed files.

Similarly, there are a few GUIs in the Unix universe that actually try and teach you the command-line equivalents of tasks you're currently doing with the GUI. AIX enthusiasts will be familiar with SMIT-- a menu driven tool that also lets you see what it's actually doing under the covers. Another example would be the highly-useful NmapFE (now Zenmap) front-end for Nmap, which is a useful tool for driving Nmap from a GUI while simultaneously learning Nmap command-line flags.

These days a lot of new Linux users are experiencing Linux almost entirely via the GUI. While I think this is excellent from the perspective of driving new adoption, at some point it's helpful to start digging around "under the covers" and figure out what's happening in terms of the actual commands being executed. This turns out to be straightforward because typically the GUI configuration information is just stored in text files. If somebody shows you the basics of find and grep, you can actually do a huge amount of self-discovery.

For example, let's suppose you're a Ubuntu user like me and you're curious about what exactly is happening when you select the menu choice for "Home Folder" and the graphical file browser pops up. Way back in Episode #10 I showed you how to find the names of files that contain a particular string:

$ sudo find / -type f | sudo xargs grep -l 'Home Folder'
[...]
grep: /usr/share/acpi-support/NEC: No such file or directory
grep: Computers: No such file or directory
grep: International.config: No such file or directory
grep: /usr/share/acpi-support/Dell: No such file or directory
grep: Inc..config: No such file or directory
[...]

Huh? What's with all the error messages?

What's going on here is that the find command is emitting file names containing spaces-- "/usr/share/acpi-support/NEC Computers International.config" and ".../Dell Inc..config"-- which are being misinterpreted by xargs. The normal fix for this problem is to slightly adjust both commands:

$ sudo find / -type f -print0 | sudo xargs -0 grep -l 'Home Folder'

"find ... -print0" tells find to terminate its output with nulls (ASCII zero) instead of whitespace. Similarly, "xargs -0 ..." tells xargs to look for null-terminated input and don't treat white space in the incoming file names as special.

The above command is going to generate a ton of output and it may take a while to sort through everything and find the file that's actually relevant. On my Ubuntu system, the menu configuration files live in the /usr/share/applications directory:

$ less /usr/share/applications/nautilus-home.desktop
[Desktop Entry]
Encoding=UTF-8
Name=Home Folder
[...]
Exec=nautilus --no-desktop
Icon=user-home
Terminal=false
StartupNotify=true
Type=Application
Categories=GNOME;GTK;Core;
OnlyShowIn=GNOME;
[...]

The "Name=" parameter is the name that appears for the particular menu choice and the "Exec=" parameter shows you the command that's being invoked.

You could even put together a quick little bit of shell fu to output the value of "Exec=" for a given menu item:

$ awk -F= '/^Exec=/ {print $2}' \
`grep -l 'Name=Home Folder' /usr/share/applications/*`

nautilus --no-desktop

Here we're using "grep -l ..." to output the file name that matches the "Name=" parameter we're searching for. We then use backticks to make the file name output of the grep command be the argument that our awk statement works on. The awk specifies "-F=" to split lines on "=" instead of whitespace, then looks for the line that starts with "Exec=" and prints the stuff after the "=". You could easily turn this into a shell script or alias if you find yourself doing it frequently.

Friday, May 22, 2009

Episode #39: Replacing Strings in Multiple Files

Hal Starts Off:

Wow, our last several Episodes have been really long! So I thought I'd give everybody a break and just show you a cool little sed idiom that I use all the time:

# sed -i.bak 's/foo/bar/g' *

Here we're telling sed to replace the all instances string "foo" with the string "bar" in all files in the current directory. The useful trick is the "-i.bak" option which causes sed to make an automatic backup copy of each file as <filename>.bak before doing the global search and replace.

By the way, you can even do this across an entire directory structure, with a little help from the find and xargs commands:

# find . -type f | xargs sed -i.bak 's/foo/bar/g'

Of course, you could use other search criteria than just "-type f" if you wanted to be more selective about which files you ran sed against.

Oh dear, I hope this isn't one of those "easy for Unix, hard for Windows" things again. Ed gets so grumpy when I do that.

Ed jumps in:
You nailed it, Hal, with that characterization. Unfortunately, cmd.exe doesn't include the ability to do find and replace of strings within lines of a file using a built-in command. We can search for strings using the find command, and even process regex with findstr. But, the replacement part just doesn't exist there.

Thus, most reasonable people will either rely on a separately installed tool to do this, or use Powershell.

For a separately installed tool, my first approach would be use Cygwin, the free Linux-like environment for Windows, and then just run the sed command Hal uses above. Nice, easy, and sensical.

Alternatively, you could download and install a tool called replace.exe.

Or, there's another one called Find And Replace Text, which, as you might guess, is called FART for short.

To do this in Powershell efficiently, I asked Tim Medin, our go-to guy for Powershell, to comment.

Tim (our Powershell Go-To Guy) says:
This morning when Ed asked me to do a "quick" write up for Powershell, I thought to myself, "This won't be too bad..." I was wrong.

By default there are aliases for many of the command in Powershell, so I'll show both the long and short version of the commands (yes, even the short command is long relative to sed).

The Long Way
PS C:\> Get-ChildItem -exclude *.bak | Where-Object {$_.Attributes -ne "Directory"} |
ForEach-Object { Copy-Item $_ "$($_).bak"; (Get-Content $_) -replace
"foo","bar" | Set-Content -path $_ }

The Short Way (using built in aliases)
PS C:\> gci -ex *.bak | ? {$_.Attributes -ne "Directory"} | % { cp $_ "$($_).bak";
(gc $_) -replace "foo","bar" | sc -path $_ }

This command is rather long, so let's go through it piece by piece.
gci -ex *.bak | ? {$_.Attributes -ne "Directory"}

The first portion gets all files that don't end in .bak. Without this exclusion, it will process file1.txt and the new file1.txt.bak. Processing file1.txt.bak results in file1.txt.bak.bak, but it doesn't do this endlessly, just twice.

The Where-Object (with an alias of ?) ensures that we only work with files and not directories because Get-Content on a directory throws an error.

ForEach-Object { Copy-Item $_ "$($_).bak"; (Get-Content $_) -replace "foo","bar" |
Set-Content -path $_ }
Once we get the files, not directories, we want, we then act on each file with the ForEach-Object (alias %). For those of you haven't yet fallen asleep, I'll further break down the inner portion of the ForEach-Object:

Copy-Item $_ "$($_).bak"
First, we copy the file to our backup .bak file. We have to use the $() in order to use our variable in a string so we can append .bak.

Finally, we get to the search and replace (and it's about time, too!).
(Get-Content $_) -replace "foo","bar" | Set-Content -path $_

Get-Content (gc) gets the contents of the file. We wrap it in parentheses so we can act on its output in order to do our replace. The output is then piped to Set-Content (sc) and written back to our file.

We could make this work a little better if we used variables, but then we are more in script-land instead of shell-land which probably violates the almighty laws of this blog. The use of variables turn this more into a scripting exercise instead of shell (OK, we may already be there). For kicks, I'll show you how we can use variables show you so you can add it to your big bloated belt of windows-fu.
$a = (gci | ? {$_.Attributes -ne "Directory"}); $a | % { cp $_ "$($_).bak";
(gc $_) -replace "foo","bar" | sc -path $_ }

The difference between our original command and this command is that the $a variable grabs a snapshot of the directory before we copy files, so we won't operate on the new .bak files.

After all this work we have done the same thing as the mighty sed. Sadly even the power of Powershell is no match for efficiency of sed.

Ed closes it out:
Thanks for that, Tim. Nice stuff!

Wednesday, May 20, 2009

Episode #38: The Browser Count Torture Test

Hal just can't resist:

One of my customers was interested in some stats on which browsers were hitting their web site most often. My first thought was to use a Perl script to parse the user agent strings out of the Apache access_log files. But, hey, I write for Command Line Kung Fu, so how far can I get just using standard Unix tools? Besides, trying to do the same thing in the Windows shell will be a pain in the... humiliating defeat... interesting learning experience for Ed, and I'm committed to the growth of my friends.

First let me show you what I came up with, then I'll explain it in detail:

# grep 'POST /login/form' ssl_access_log* | \
sed -r 's/.*(MSIE [0-9]\.[0-9]|Firefox\/[0-9]+|Safari|-).*/\1/' | \
sort | uniq -c | \
awk '{ t = t + $1; print} END { print t " TOTAL" }' | \
sort -nr | \
awk '/TOTAL/ { t = $1; next }; { $1 = sprintf("%.1f%%\t", $1*100/t); print}'

46.4% Firefox/3
27.0% MSIE 7.0
14.3% Safari
5.3% MSIE 6.0
3.0% Firefox/2
2.4% -
1.2% MSIE 8.0
0.3% Firefox/1

Here's the line-by-line interpretation:


  1. I didn't want to count every single page access, but was instead more interested in counting browsers by "user session". Since the site requires a user login for access, I used posting the secure login form as a proxy for recognizing individual sessions. Close enough for jazz.

  2. Now, to pull out the browser name/version from the user agent string. The data here is annoyingly irregular, so it looks like a good task for sed. Notice that I'm using the "-r" option in GNU sed to use extended regular expression syntax: not only does this allow me to use "|" in the regexp, but it also means I don't need to backwhack my parens to create sub-expressions.

    The regular expression itself is interesting. I'm creating a sub-expression match on either "MSIE <vers>.<sub>", "Firefox/<vers>", or "Safari" (I don't find tracking Firefox sub-versions or Safari version numbers that interesting, but as always "your mileage may vary"). Anything that doesn't match one of these browser patterns ends up matching a hyphen ("-") character, which are plentiful in Apache access_log entries.

    I place ".*" before and after the sub-expression, which matches the rest of the line before and after the browser string. However, since that text is not included in the sub-expression, when I replace the matching line with the sub-expression then the rest of the text is dropped. That leaves us with an output stream of just the browser info, or "-" for lines that don't match one of the major browsers we're tracking.

  3. Now that we've got a data stream with the browser info, it's time to count it. "... | sort | uniq -c" is the common idiom for this, and we end up with output like:

        290 -
    34 Firefox/1
    363 Firefox/2
    5534 Firefox/3
    632 MSIE 6.0
    3207 MSIE 7.0
    139 MSIE 8.0
    1708 Safari

  4. The next line is a common awk idiom for totalling a column of numbers. We print out each line as it's processed, but also keep a running total in the variable "t". After all the input has been processed, we use an "END" block to output the total. Now our output looks like:

        290 -
    34 Firefox/1
    363 Firefox/2
    5534 Firefox/3
    632 MSIE 6.0
    3207 MSIE 7.0
    139 MSIE 8.0
    1708 Safari
    11907 TOTAL

  5. The next "sort -nr" not only puts our data into numerically sorted order, but also has the side-effect of moving the "TOTAL" column up to the first line of output. We're going to make use of this in the awk expression on the next line.

  6. The last awk expression is a little psychotic, so let's take it piece by piece. The first section, "/TOTAL/ { t = $1; next }", matches our initial "TOTAL" line and puts the total number of entries into the variable "t". The "next" causes awk to skip on to the next line without printing the current line ("TOTAL").

    The other portion of the awk code will handle all of the other lines in the output. What we're doing here is replacing the raw count number in the first column with a percentage. The "sprintf(...)" format string looks a little weird, but it means a floating point value with one decimal place ("%.1f"), followed by a literal percent character ("%%"), followed by a tab ("\t"). The numeric value we plug in is the raw count from column 1 of the output, times 100, divided by the "TOTAL" value we extracted from the first line of output.


And there you have it. The agonized squealing you're hearing is Ed wondering how he's going to even get close to this in the Windows shell. I can't wait to see what he comes up with.

Ed responds:
Wow! That's some serious fu there, Hal. And, I mean both serious and fu.

Thanks for the interesting learning opportunity, kind sir. How delightful!

As you know, we're kinda hampered with cmd.exe in that we get regex support from findstr, which cannot do extended regular expressions like sed -r. Therefore, we cannot do the funky "|" in the regex. Our resulting command will have to include more piece-parts for each browser.

And, as we discussed in Episode # 25: My Shell Does Math, we have access to simple integer math in cmd.exe via "set /a", but floating point and division cause problems.

Still, we can get some useful output that tells us the number of each kind of browser and a handy total like this:

C:\> echo MSIE > browser.txt & echo Firefox >> browser.txt & echo Safari
>> browser.txt & echo - >> browser.txt & (for /f %i in (browser.txt) do
@echo %i & type ssl_access_log | find "POST /login/form" | find /c "%i" & echo.)
& del browser.txt
MSIE
873

Firefox
1103

Safari
342

-
2327

In this command, I'm first building a little file called browser.txt containing the different browsers strings that I'd like to count. I'll then iterate over that file using a FOR /F loop. I'd much rather do this by iterating over a string containing "MSIE FIREFOX SAFARI -", but unfortunately, FOR /F parses strings into a series of variables all in one FOR /F iteration, making it useful for parsing a string into different variables (like %i %j %k, etc.). But, FOR /F used with a string does not pull apart a string into pieces that vary at each iteration through the loop. Boo, FOR /F! So, we compensate by building a little file with one browser per line, and then we iterate over that.

For each browser in browser.txt, we display the browser name (echo %i), and scrape through our ssl_access_log using the plain old find command to look for lines with "POST /login/form". I then take the output of that, pipe it through find with a /c option to count the number of occurrences of the %i iterator, which is the name of each browser. Note that the - will total all browsers, since their log entries have a dash in them. After my little looping escapade, I delete the temporary browser.txt file that I created at the beginning.

The output, while not as beautiful as Hal's, still is useful -- you see the number of POST login actions per browser, and the total. Why, you could even add a little "& calc.exe" at the end to pop up a calculator to do your percentages. :)

Monday, May 18, 2009

Episode #37: Show Account Security Settings

Ed engages:

Yesterday, I was doing a presentation for a bunch of auditors, and a nice question came up from the attendees: "How can I quickly see the local account security settings on a Windows box from the command line?" When I gave the answer, I saw a lot of people's eyes light up. Of course, whenever an auditor's eyes start to flicker, we should all watch out. :)

Seriously, though... the vast majority of the people in the room quickly wrote down a note with my answer, so I figured it would make a good episode here.

On Windows, you can see overall security settings for all accounts on the box using the command:

C:\> net accounts
Force user logoff how long after time expires?: Never
Minimum password age (days): 0
Maximum password age (days): 42
Minimum password length: 0
Length of password history maintained: None
Lockout threshold: Never
Lockout duration (minutes): 30
Lockout observation window (minutes): 30
Computer role: WORKSTATION
The command completed successfully.

A simple little command like that shows really useful information, for auditors, pen testers, general security personnel... great stuff. We've got password aging information, minimum password length, password history (so users can't just reset their password to an older one they used to have), the threshold of bad logon attempts for account lockout, the time duration of account lockout, and the amount of time before a locked out account is re-activated.

The output I show above is the default settings for most versions of Windows, including Win2K, WinXP, and Vista (Yup... minimum password lenght of 0 by default!). On Win2k3, the only difference is that the "Computer role:" says SERVER.

Another nifty related command is:

C:\> net accounts /domain

You can run this on any system that is a member of the domain, and it'll show you the domain-wide settings for accounts.

Pretty cool, and all in one place.

So, what've you got for us on Linux, big guy?

Hal reports in:

I'm sure you all are getting fairly tired of this, but I have to give my usual disclaimers:

1) Different Unix systems handle password security settings in different ways, so we're just going to focus on Linux

2) The answer is different if you're working with a network-based authentication database like LDAP or Kerberos, but for purposes of this article we're just going to stick to local password files

With those disclaimers in mind, the basic answer is simple:

# chage -l hal
Last password change : Jul 14, 2007
Password expires : never
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7

The "chage" command can be used to get (and set) basic password security parameters for accounts on your Linux system (other Unix variants often use the "passwd" command for this). This is actual output from one of my test systems and shows you the standard Linux defaults for these parameters, which are obviously not terribly secure. You may change the defaults by modifying the /etc/login.defs file, but be aware that the defaults you set in login.defs will only apply to new accounts that you create with the built-in "useradd" program that comes with Linux. If you use some other scheme for creating accounts, then you'll have to use the "chage" command to manually set these values after you create each account.

If you compare the "chage" output with the output of Ed's "net accounts" command, you'll notice that "chage" doesn't have anything to say about password history settings or "lockout on failure" parameters. That's because this level of password security is a property of the lower-level PAM configuration on most Unix systems. On Linux, the pam_cracklib and pam_unix modules take care of password history and strong password enforcement, while pam_tally is responsible for "lockout on failure". Unfortunately there's no way to audit the settings for these modules other than to look at the actual PAM configuration files, usually found in /etc/pam.d.