All posts by mkirby

SlimShim – IP/MAC spoofing of a connected device without interrupting traffic

SlimShim is a script that performs IP and MAC address spoofing of a directly-connected device without interrupting traffic to and from that device. A SlimShim has 2 ethernet ports that is plugged in between the victim device and the switch/router. The SlimShim acts as a switch and injects spoofed packets below the radar (source ports).

QUICK OVERVIEW/TL;DR

  1. The device you shim will remain up and connected. Nothing on the victim device will change.
  2. This allows you to get past NAC (as long as you shim a trusted device).
  3. This allows you to MITM the device connecting through the SlimShim.
  4. This allows you to spoof as any device connected to the SlimShim.
  5. If you attack the victim, you will appear as the router.
  6. If your SlimShim has wifi, you can route through it and appear as the victim.
  7. Everything is transparent to the network.
  8. A network scanner cannot detect SlimShim
  9. Code is available at https://github.com/mtkirby/slimshim
  10. Only IPv4 is supported at the moment. IPv6 is coming soon.
  11. 802.1q vlans can be shimmed.
  12. Slimshim mimics the TTL of the victim device.
  13. You will need expert-level skills of Linux and networking to understand how this works.
  14. You need to plug the victim into eth1.  The network guess function sniffs for input packets on that interface.

TODO/Upcoming features:

  1. IPv6 support

UPDATE: After all the work of testing and scripting, it turns out I just re-invented the wheel. The idea was already conceived by Alva Lease ‘Skip’ Duckwall IV and he did a presentation at Defcon 19.  His script is called 8021xbridge.  I like my script better as it is better at network guessing and supports 802.1q.

SlimShim is a bash script that runs iptables and ebtables commands to mimic the IP and MAC of the victim. It will use an ephemeral port (source ports) range below the range used by Windows and Linux devices. SlimShim will forward a lower range of ephemeral ports to itself, such as ports 27000-32000.

SlimShim can run on any device that has 2 ethernet ports. I use it on a Raspberry Pi(with a usb ethernet) running Kali and a Nexx WT3020 running OpenWRT. To shim a PC, simply unplug it’s ethernet and plug the cable into the SlimShim (ETH1) and plug the other SlimShim ethernet(ETH0) into the PC. You may need a cross-over cable if your device can’t configure it automatically. The two interfaces on the SlimShim are bridged, so it acts as a switch. The victim device does not know it has been shimmed and neither does the router/switch.

I have wifi AP setup on my SlimShims so that I can connect to victim’s network with a laptop and still appear as the victim IP/MAC.

The hardest part of shimming a victim is to get the IP/MAC of the victim and router. On my SlimShims, I have the slimrun.sh start on bootup via rc.local (/root/slimrun.sh >/tmp/slimrun.log 2>&1). The slimshim script will run 3 tcpdumps to attempt to guess the IP/MAC of the victim and router.  The script should also work if you are shimming a server that is receiving mostly inbound packets, such as a networked security camera (which is a funny story for another time).

The method I use to guess the network is as follows:

  1. Start a loop to sniff for packets.  If anything doesn’t look right, start over.
  2. Sniff for inbound packets on eth1, where the victim device is plugged in.  This gets the victim IP, MAC, TTL, and 802.1q vlan tag(if used).
  3. Get the route MAC by sniffing for packets that are sent to the victim IP, look for non-standard TTLs (because we want packets that were hopped), and ignore any local network packets (assuming we’re on a /24).
  4. Now for the hard part, which is getting the router IP.  We have to watch for an arp request for who has the router MAC we got from the previous sniff. This may take a while for the victim to re-query the router IP, so to speed it up I re-plumb eth1 and also use scapy(if installed) to flood the assumed /24 with arp requests.

 

The script will also watch for DNS queries and update /etc/resolv.conf with the nameserver that the victim is using.

The script will also forward all connections to 1.1.1.1 to the SlimShim. This allows the victim PC to connect to the SlimShim, which is useful to me when I’m pentesting. The script will also forward port 2501 to it’s ssh service so I can connect to SlimShim from elsewhere on the network.

The redirectIngressPort and redirectIngressPorts functions can redirect traffic that is destined to the victim. This is useful for opening ports to be used for reverse-bind attacks when I attack the network.

The redirectEgressPort function can redirect traffic from the victim so that I can redirect DNS, or anything, for MITM attacks. I once redirected a victim running Splunk Forwarder to a malicious Splunk server that I setup and deployed an app that opened a reverse shell.

The source is available at https://github.com/mtkirby/slimshim It is GPLv3.

The slimshim script will create an environment log file in /root that you can source and use the variables for your own scripts.

So how can you protect your network against SlimShims? You can’t block it, but you can detect it by configuring your firewalls to log all connections that use a source port range of 27000-32000. Unless the attacker changed to a different set of source ports.

#############################################################################
How to setup SlimShim on a Raspberry Pi running Kali:
First get a usb ethernet adapter because SlimShim requires 2 ethernets.
Setup your /etc/network/interfaces like so:

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual
up ifconfig $IFACE up

auto eth1
iface eth1 inet manual
up ifconfig $IFACE up

auto wlan0
iface wlan0 inet static
address 192.0.0.1
netmask 255.255.255.0

Setup wifi. Add this to /etc/hostapd/hostapd.conf. Ssid is slimshim and password is Gaddafi’d! (anyone get the reference?)

interface=wlan0
driver=nl80211
ssid=slimshim
hw_mode=g
channel=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=2
wpa=2
wpa_passphrase=Gaddafi’d!
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
ieee80211n=1
require_ht=1

Setup dhcp for the wifi interface. Add this to /etc/dhcp/dhcpd.conf

ddns-update-style none;
option domain-name “shim.lan”;
option domain-name-servers 8.8.8.8;
subnet 192.0.0.0 netmask 255.255.255.0 {
range 192.0.0.10 192.0.0.254;
}
default-lease-time 600;
max-lease-time 7200;
log-facility local7;

The script will create a br-lan bridge and assign it 169.254.0.1/24 with MAC of 00:01:01:01:01:01. The wifi interface will be 192.0.0.0/24 (an RFC 5736 private network). You can ssh to it once you are connected to wifi. Add it as your default route if you want to route through the SlimShim.

Copy the code from Github and put in /root/. Now run slimrun.sh

I have the slimrun.sh start on bootup via rc.local (/root/slimrun.sh >/tmp/slimrun.log 2>&1).

If that ran without errors, try to telnet to a website to see if everything is working. You can sniff the interface to verify your IP/MAC spoofs. The source port range should be within the range specified in the slimshim script.

That should do it. Let me know if something doesn’t work for you.

#############################################################################

How to setup SlimShim on a Nexx WT3020. These things are only $15-$20. They are powered by usb. They don’t have much storage or ram, so don’t expect much beyond shimming a victim and maybe run a small nmap scan.  I have also used GL-Inet MT300A devices.  They are slightly larger, but have more ram.

First install OpenWRT or LEDE. I have mine modified to use a usb thumbdrive for storage. The Nexx has very little storage and you won’t be able to do much without a thumbdrive extension. There is documentation on OpenWRT’s site on how to use a thumbdrive as an overlay filesystem. I use a sandisk ultrafit 16gb. Here is how to setup a root overlay:

  1. Install the following packages: blkid block-mount kmod-crypto-hash kmod-fs-ext4 kmod-lib-crc16 kmod-scsi-core kmod-usb-ohci kmod-usb-storage-extras kmod-usb-storage kmod-usb-uhci kmod-usb2 libblkid libpthread librt libuuid
  2. Partition and format your thumbdrive.  I use 2 partitions, first is ext4 and the second is swap.  Don’t forget to run mkswap on the swap partition.
  3. Plug the thumbdrive into the device and run: mount /dev/sda1 /mnt; tar -C /overlay -cvf – . |tar -C /mnt/-xf -; umount /mnt
  4. Add the following to /etc/config/fstab
    config 'mount'
        option target '/overlay'
        option device '/dev/sda1'
        option enabled '1'
        option options 'rw,sync'
        option enabled_fsck '1'
    
    config 'swap'
       option device '/dev/sda2'
       option enabled '1'
    
  5. Reboot and check to make sure the overlay fs is working with ‘df /root’

Run these uci commands:

uci delete wireless.radio0
uci set wireless.radio0=wifi-device
uci set wireless.radio0.type=’mac80211′
uci set wireless.radio0.channel=’11’
uci set wireless.radio0.hwmode=’11g’
uci set wireless.radio0.path=’10180000.wmac’
uci set wireless.radio0.htmode=’HT20′
uci set wireless.radio0.txpower=’20’
uci set wireless.radio0.country=’00’

uci delete wireless.@wifi-iface[0]
uci add wireless wifi-iface
uci set wireless.@wifi-iface[0]=wifi-iface
uci set wireless.@wifi-iface[0].device=’radio0′
uci set wireless.@wifi-iface[0].mode=’ap’
uci set wireless.@wifi-iface[0].ssid=’slimshim’
uci set wireless.@wifi-iface[0].hidden=’1′
uci set wireless.@wifi-iface[0].encryption=’psk2′
uci set wireless.@wifi-iface[0].key=’Gaddafi’\”d!’
uci set wireless.@wifi-iface[0].network=’wifi’

uci delete dhcp.lan
uci delete dhcp.wan
uci delete dhcp.odhcpd
uci delete dhcp.wifi
uci set dhcp.lan=dhcp
uci set dhcp.lan.interface=’lan’
uci set dhcp.lan.ignore=’1′
uci set dhcp.wan=dhcp
uci set dhcp.wan.interface=’wan’
uci set dhcp.wan.ignore=’1′
uci set dhcp.odhcpd=odhcpd
uci set dhcp.odhcpd.maindhcp=’0′
uci set dhcp.odhcpd.leasefile=’/tmp/hosts/odhcpd’
uci set dhcp.odhcpd.leasetrigger=’/usr/sbin/odhcpd-update’
uci set dhcp.wifi=dhcp
uci set dhcp.wifi.leasetime=’12h’
uci set dhcp.wifi.limit=’150′
uci set dhcp.wifi.interface=’wifi’
uci set dhcp.wifi.start=’10’

uci set dropbear.@dropbear[0].GatewayPorts=’on’

while uci show firewall.@defaults[0] >/dev/null 2>&1 ; do
uci delete firewall.@defaults[0] >/dev/null 2>&1
done
while uci show firewall.@zone[0] >/dev/null 2>&1 ; do
uci delete firewall.@zone[0] >/dev/null 2>&1
done
while uci show firewall.@forwarding[0] >/dev/null 2>&1 ; do
uci delete firewall.@forwarding[0] >/dev/null 2>&1
done
while uci show firewall.@rule[0] >/dev/null 2>&1 ; do
uci delete firewall.@rule[0] >/dev/null 2>&1
done
while uci show firewall.@include[0] >/dev/null 2>&1 ; do
uci delete firewall.@include[0] >/dev/null 2>&1
done

uci add firewall defaults
uci set firewall.@defaults[0]=defaults
uci set firewall.@defaults[0].input=’ACCEPT’
uci set firewall.@defaults[0].output=’ACCEPT’
uci set firewall.@defaults[0].forward=’ACCEPT’
uci add firewall zone
uci set firewall.@zone[0]=zone
uci set firewall.@zone[0].name=’lan’
uci set firewall.@zone[0].input=’ACCEPT’
uci set firewall.@zone[0].output=’ACCEPT’
uci set firewall.@zone[0].forward=’ACCEPT’
uci set firewall.@zone[0].network=’ ‘
uci add firewall zone
uci set firewall.@zone[1]=zone
uci set firewall.@zone[1].name=’wan’
uci set firewall.@zone[1].output=’ACCEPT’
uci set firewall.@zone[1].network=’wan wan6′
uci set firewall.@zone[1].input=’ACCEPT’
uci set firewall.@zone[1].forward=’ACCEPT’
uci add firewall forwarding
uci set firewall.@forwarding[0]=forwarding
uci set firewall.@forwarding[0].src=’lan’
uci set firewall.@forwarding[0].dest=’wan’
uci add firewall include
uci set firewall.@include[0]=include
uci set firewall.@include[0].path=’/etc/firewall.user’

uci set network.lan._orig_ifname=’eth0.1′
uci set network.lan._orig_bridge=’true’
uci set network.lan.ifname=’eth0.1 eth0.2′
uci set network.lan.ipaddr=’169.254.0.1′
uci set network.lan.netmask=’255.255.255.0′
uci set network.lan.force_link=’1′
uci set network.lan.delegate=0
uci delete network.lan.ip6assign

uci set network.@switch_vlan[0].ports=’0 6t’
uci set network.@switch_vlan[0].vid=’1′
uci set network.@switch_vlan[1].ports=’1 2 3 4 6t’
uci set network.@switch_vlan[1].vid=’2′

uci set network.wifi=interface
uci set network.wifi._orig_ifname=’wlan0′
uci set network.wifi._orig_bridge=’false’
uci set network.wifi.proto=’static’
uci set network.wifi.ipaddr=’192.0.0.1′
uci set network.wifi.netmask=’255.255.255.0′
uci set network.wifi.delegate=’0′

uci delete network.wan
uci delete network.wan6

uci set system.@system[0].hostname=slimshim

uci commit

Next steps are to install the bash and scapy packages and copy the code from Github and put in /root/.  Now run slimrun.sh and watch the output.

I have the slimrun.sh start on bootup via rc.local (/root/slimrun.sh >/tmp/slimrun.log 2>&1).

If that ran without errors, try to telnet to a website to see if everything is working. You can sniff the interface to verify your IP/MAC spoofs. The source port range should be within the range specified in the slimshim script. The slimshim script will blink the power led at a slow pace when it wants you to unplug and replug the vicim ethernet to get the router’s MAC.

Shimming a Network with Shim.pl

UPDATE: This is deprecated.  Look at SlimShim instead.

QUICK OVERVIEW/TL;DR
1) This allows you to get past NAC (as long as you shim a trusted device)
2) This allows you to MITM any host connecting through the shimbox
3) This allows you to spoof as any device connected to the Eth1
4) This allows you to shim multiple devices on separate vlans with separate routers
5) Everything is transparent to the network
6) Only IPv4 is supported at the moment. IPv6 is coming soon.

The shimbox is a dual-nic device that plugs into the network between a pc and the switch or between a switch uplink port and the router.  The shimbox will spoof the IPs and MACs of the IPs on the LAN, and the router on it’s opposite interfaces.  It creates spoofed IPs/MACs of every device in the LAN on the shimbox’s outside interface and creates spoofed IPs/MACs of the router(s) on the shimbox’s inside interface. This allows the shimbox to share the same IP and MAC with all devices connected to it’s internal interface. The shimbox can then pivot through any of those LAN IPs to attack the network or to MITM attack the devices on the switch. A single device can also be shimmed with the use of a crossover cable. This is particularly useful on networks that have NAC (Network Access Control) where a rogue device is incapable of joining the network.

shimoverview

The goals of this project were as follows:

  1. The LAN devices must not perceive any manipulation to the router IP and MAC.
  2. The LAN devices must not detect any changes to it’s network configuration, otherwise the user will get a popup to classify the network as home, work, or public.
  3. The LAN devices must be able to connect to other IPs on the same LAN, even if they are on a separate bridged-switch.
  4. The LAN devices must not see a duplicate IP/MAC.
  5. The router(s) must not see a duplicate IP/MAC.
  6. The LAN devices must be able to connect to the shim box, but only when I allow it.  This is for when I shim my own PC to do pentesting.
  7. The LAN IPs and MACs cannot be manipulated.  The surrounding network cannot see any changes to any device on the LAN.
  8. NAC cannot see any changes and must trust the shimbox and LAN.
  9. The shimbox must forward any inbound connections to the IPs on the LAN.  (In case any services such as RDP, NAC agents, etc.).
  10. The shimbox must be able to connect to the network using the IP of any IP on the shimmed LAN.
  11. The shimbox must be able to be removed with only a short disconnect for shimmed devices as the cables are restored to prior configuration.
  12. A network scanner must not be able to detect the shimbox.

There are 2 caveats:
1) Broadcast packets, which netbios is notorious for.  IPTables is not capable of forwarding broadcast packets.  This should only be a problem for “network neighborhood” discovery on the LAN.  Connecting directly to a CIFS/SMB server will work just fine and very entertaining with MITM attacks to steal password hashes.
2) Multicast, which is rare for most networks you would shim. You will have to figure this out on your own. You may need to setup smcroute by hand.

As anyone with network skills knows, you cannot just plug in a new device, spoof the IP/MAC without first unplugging the PC you are mimicking.   I toyed with the idea of using a bridge and arptables to spoof the MAC, but realized that TCP would break when either the shimbox or PC would reply with RST packets for a connection was intended for the other.  The solution was to use network namespaces. Linux network namespaces are similar to Cisco VRFs. It’s a container for a virtualized network stack. Think of a chroot for networking, or a virtual machine without an OS. A network namespace contains it’s own arp tables, firewall rules, routes, interfaces, and IPs. My current version can only do IPv4, but I have plans for IPv6. It can shim dhcp or static IPs. I have a separate script called dhcptail.pl to watch for dhcp requests and auto-run shim.pl with it’s MAC.  For static IPs, you’ll have to sniff and watch, and run shim.pl manually with the appropriate IP and MAC flags.  The shimbox will create up to 2 namespaces for each device you want to shim. The router-side namespace will be re-used if multiple devices are using the same router.

For my shimbox hardware, I chose a Zotac MI542. It has dual-core i5, 16gb ram, 500gb ssd, wifi, and 2 gigabit nics. It is running Kali 2. There is no hardware manipulation. Any hardware with 2 nics will work. A usb nic adapter would work if your hardware has only 1 nic. I am currently experimenting on porting Shim.pl to OpenWRT, which will require a custom kernel.

The shimbox has 2 physical NICs.  ETH0 is external, facing the router, and an empty bridge is created called BR0. ETH1 is internal, facing the LAN on the uplink port, or an individual device, and has an empty bridge called BR1.  A virtual interface, TAP0, is created as an internal bridge. Each device IP gets a dedicated namespace on the external bridge. The router(s) gets a dedicated namespace on the internal bridge, but will be re-used if more than 1 device is using that router. Two TAP interfaces are created in each namespace. TAP1 is the spoofed IP/MAC and TAP2 links to the internal TAP0 bridge. The TAP2 interfaces are assigned an IP from the 169.254.0.0/16 space. IPTables and ARPTables are used to SNAT and DNAT the traffic through the shimbox.

shimbridges

Here is breakdown of how shim.pl works:

  1. If shim.pl is ran with –shimmac=, it will use dhcp to spoof a dhclient on the outside and a dhcpd server on the inside. Otherwise flags for router/shim IPs/MACs must be supplied.
  2. A namespace is created for the device to be shimmed and the 2 tap interfaces are created. Tap1 is the device IP/MAC and Tap2 is the link to the internal bridge.
  3. The dhclient-script was modified to output environment variables to a file, which is then used with shim.pl.
  4. A namespace is created for the router, unless it already exists for a previously shimmed device in which case a new IP is added to Tap2 along with a PBR for the shimmed IP. Tap1 is the router IP/MAC and Tap2 is the link to the internal bridge.
  5. IPTables will DNAT any inbound traffic to the device IP as the Tap2 IP of it’s respective router namespace, which is then DNAT’d again to the actual device IP. And vice versa.
  6. IPTables will increment the TTL on outbound packets so that the shimmed devices cannot see the shimbox in a traceroute
  7. Proxy arp is enable on the router namespace and routes are added/updated to route for other IPs on the same subnet. This allows and shimmed device to connect to other IPs on the same subnet. If multiple shimmed devices exist, their IP is removed from the routes. Each shimmed device is assigned an individual PBR table.
  8. Appropriate ARPTables rules are added to prevent collisions in the event that someone swaps the cables and interfaces.
  9. Additional IPTables can be ran to forward ports to the shimbox or redirect any other ports. For example, I can redirect port 2222 to the shimbox ssh so I can connect to it from the inside or outside. Very useful when you need to forward ports for reverse-bind exploits.

The shim.pl script will fork after getting enough information so that the bulk of the program is executed in the background. The whole process can take 10-60 seconds per shim with dhcp, but it can run multiple shims in parallel. The internal network utilizes the 169.254.0.0/16, which allows shim.pl to shim up to 32,766 devices since 2 internal IPs are used per shimmed device. When I want to pivot shimbox to mimic a device, I simply add the shim’s linked bridge IP(tap2) as my default route on the shimbox.

Here is the output of shim.pl –showshims with 2 active shims:

SHIMNS: S080027db4f52
ROUTERNS: R6805ca325e85
HOSTNAME: win7
OUTSIDE eth0/br0
|	brS080027db4f52/tap1/192.168.1.232
|_	lnS080027db4f52/tap2/169.254.0.4
	|_	CORE BRIDGE tap0/169.254.0.1
		|	lnR6805ca325e85/tap2/169.254.0.5
		|_	brR6805ca325e85/tap1/192.168.1.1
			|_	INSIDE eth1/br1

SHIMNS: Sac87a30a4362
ROUTERNS: R6805ca325e85
HOSTNAME: agent86
OUTSIDE eth0/br0
|	brSac87a30a4362/tap1/192.168.1.19
|_	lnSac87a30a4362/tap2/169.254.0.2
	|_	CORE BRIDGE tap0/169.254.0.1
		|	lnR6805ca325e85/tap2/169.254.0.3
		|_	brR6805ca325e85/tap1/192.168.1.1
			|_	INSIDE eth1/br1

Below is a diagram of me shimming a single PC, named Agent86. A namespace is created on the outside with Tap1 as Agent86’s IP. Another namespace is created on the inside with Tap1 as the router’s IP. The Tap2 interfaces in each namespace are linked to the CORE BRIDGE. IPTables NAT rules are added within each namespace so that incomming packets from the network to Agent86 are DNAT’d to the Tap2 interface of the router namespace. The router namespace has a DNAT to translate inbound packets on it’s Tap2 interface to forward to Agent86. When Agent86 connects outbound, the router namespace SNATs the source to the router namespace Tap2 and routes the traffic to the Shim namespace, which will SNAT again as the Tap1 interface as it is forwarded onto the router. The dotted blue and green lines show that the IPs are spoofed.

shimagent86

 

After a shim is built, you can view it’s settings through ‘ip netns exec’.

Here is an example of ‘ip netns exec Sac87a30a4362 iptables-save’ to view the shim netns (external namespace)

*mangle
:PREROUTING ACCEPT [377:154569]
:INPUT ACCEPT [74:10684]
:FORWARD ACCEPT [279:140040]
:OUTPUT ACCEPT [1:40]
:POSTROUTING ACCEPT [280:140080]
COMMIT
*filter
:INPUT ACCEPT [74:10684]
:FORWARD ACCEPT [279:140040]
:OUTPUT ACCEPT [1:40]
COMMIT
*nat
:PREROUTING ACCEPT [66:9704]
:INPUT ACCEPT [54:7824]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [2:156]
-A PREROUTING -d 192.168.1.19/32 -p udp -m udp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -d 192.168.1.19/32 -p tcp -m tcp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -d 192.168.1.19/32 -p udp -m udp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -d 192.168.1.19/32 -p tcp -m tcp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -d 192.168.1.19/32 ! -p icmp -j DNAT --to-destination 169.254.0.3
-A PREROUTING -d 169.254.0.2/32 ! -p icmp -j DNAT --to-destination 192.168.1.1
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp --dport 22 -j SNAT --to-source 169.254.0.2
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp --dport 22 -j SNAT --to-source 169.254.0.2
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.2
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.2
-A POSTROUTING ! -d 169.254.0.0/16 -o tap1 -j SNAT --to-source 192.168.1.19
COMMIT

Here is an example of ‘ip netns exec R6805ca325e85 iptables-save’ to view the spoofed router netns (internal namespace)

*mangle
:PREROUTING ACCEPT [1428:1143012]
:INPUT ACCEPT [138:20976]
:FORWARD ACCEPT [1276:1117304]
:OUTPUT ACCEPT [14:4676]
:POSTROUTING ACCEPT [1290:1121980]
-A PREROUTING -m ttl --ttl-gt 1 -j TTL --ttl-inc 2
COMMIT
*filter
:INPUT ACCEPT [161:26760]
:FORWARD ACCEPT [1342:1128970]
:OUTPUT ACCEPT [16:5388]
COMMIT
*nat
:PREROUTING ACCEPT [143:21296]
:INPUT ACCEPT [138:20976]
:OUTPUT ACCEPT [9:3006]
:POSTROUTING ACCEPT [23:4098]
-A PREROUTING -s 192.168.1.232/32 -p udp -m udp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -s 192.168.1.232/32 -p tcp -m tcp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -s 192.168.1.232/32 -d 192.168.1.1/32 -p udp -m udp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -s 192.168.1.232/32 -d 192.168.1.1/32 -p tcp -m tcp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -s 192.168.1.19/32 -p udp -m udp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -s 192.168.1.19/32 -p tcp -m tcp --dport 2222 -j DNAT --to-destination 169.254.0.1:22
-A PREROUTING -s 192.168.1.19/32 -d 192.168.1.1/32 -p udp -m udp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -s 192.168.1.19/32 -d 192.168.1.1/32 -p tcp -m tcp -m multiport --dports 4444 -j DNAT --to-destination 169.254.0.1
-A PREROUTING -d 192.168.1.1/32 ! -p icmp -j DNAT --to-destination 169.254.0.2
-A PREROUTING -d 169.254.0.3/32 ! -p icmp -j DNAT --to-destination 192.168.1.19
-A PREROUTING -d 192.168.1.1/32 ! -p icmp -j DNAT --to-destination 169.254.0.4
-A PREROUTING -d 169.254.0.5/32 ! -p icmp -j DNAT --to-destination 192.168.1.232
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp --dport 22 -j SNAT --to-source 169.254.0.5
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp --dport 22 -j SNAT --to-source 169.254.0.5
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.5
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.5
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp --dport 22 -j SNAT --to-source 169.254.0.3
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp --dport 22 -j SNAT --to-source 169.254.0.3
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p udp -m udp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.3
-A POSTROUTING -d 169.254.0.1/32 -o tap2 -p tcp -m tcp -m multiport --dports 4444 -j SNAT --to-source 169.254.0.3
-A POSTROUTING -s 192.168.1.19/32 -j SNAT --to-source 169.254.0.3
-A POSTROUTING -s 192.168.1.232/32 -j SNAT --to-source 169.254.0.5
COMMIT

The code is available at https://github.com/mtkirby/shimpl/

igHome widgets

Now that iGoogle is gone, I had to find an alternative RSS reader.  After reading the reviews of several alternatives, I settled on igHome.  It was able to read the export from my iGoogle page and re-establish all of the RSS feeds.

Here is a list of the security/technology feeds that I have on my igHome page:

CGISecurity – Website and Application Security News
CNET News – Security & Privacy
Computer Forensics, Malware Analysis & Digital Investigations
Dark Reading
Engadget
Errata Security
Ethical Hacking-Your Way To The World Of IT Security
Exploit Files ? Packet Storm
Exploits Database by Offensive Security
Gizmodo
Google Online Security Blog
ha.ckers.org web application security lab
Hacker 10 – Security Hacker
Hackers Center – Security Tools and Texts
HackRead – Latest Cyber Crime – Information Security – Hacking News
Homeland Security News
HowtoForge – Linux Howtos and Tutorials –
IT Security Blog | TechRepublic
Krebs on Security
Latest security vulnerabilities ,with exploits,
Latest Topic for ZDNet in malware
Linux Format – The #1 source for Linux
Linux Journal – The Original Magazine of the Linux Community
Linux.com – All Content
LinuxInsider
LinuxSecurity.com
Linuxtoday.com
LWN.net
Malware Intelligence Lab from FireEye – Research & Analysis of Zero-Day & Advanced Targeted Threats
MalwareBlacklist updates
Microsoft Security Bulletins
Naked Security – Sophos
Network Security Podcast
Network World on Security
nixCraft: Linux / Unix Tutorials and HowTos
obscuresec
Open Malware – Community Malicious code research and analysis
Open Security Research
Penetration Testing Lab
Pentest Geek
Pentester’s Blog
Ph0x7R077 53(UR17’/
Running the Gauntlet
SANS Computer Forensics and e-Discovery with Rob Lee
SANS Internet Storm Center
SANS Internet Storm Center Daily Network Security Podcast
SANS Internet Storm Center Handler Select News
SANS ISC
SANS NewsBites
SANS PenTesting Blog
SANS Security Trend Line
Schneier on Security
SearchSecurity: Security Wire Daily News
Security
Security Advancement
Security Garden
Security News Headlines – Yahoo! News
Security Now!
Security Research & Defense
Security Research & Defense
Security Tool Files ? Packet Storm
Security Watch
SecurityFocus News
SecurityFocus Vulnerabilities
SecurityNewsPortal.com latest breaking computer security, anti virus and hacking news
SecurityTube.Net
Slashdot
TaoSecurity
Techdirt
Technology – Google News
The Register
The Register – Security
threatpost – The First Stop for Security News
TrendLabs Security Intelligence Blog
Twitter Gadget
Web App Security
Web Application Security
WindowSecurity.com
Wired Top Stories
ZDNet
ZDNet | Zero Day Blog RSS
Zer0 Security

Postfix setup on mkirby.org

I run Postfix as my MTA at mkirby.org
As I’ve stated before, this blog isn’t to show people how to set up services but rather how to secure them.

I disabled the VRFY command to prevent user account lookups:
disable_vrfy_command = yes

Here is how I setup ingress TLS:
smtpd_use_tls = yes
smtpd_tls_auth_only = yes
tls_random_source = dev:/dev/urandom
smtpd_tls_cert_file = /etc/postfix/certs/mkirby.org.crt
smtpd_tls_key_file = /etc/postfix/certs/mkirby.org.key
smtpd_tls_CApath = /etc/postfix/certs
smtpd_tls_CAfile = /etc/postfix/certs/ca.crt
smtpd_tls_loglevel = 2
smtpd_tls_received_header = yes
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_tls_security_level = may
smtpd_tls_ask_ccert = yes
broken_sasl_auth_clients = yes
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5, RC4
smtpd_tls_mandatory_protocols = TLSv1.2, TLSv1.1, TLSv1.0, SSLv3, !SSLv2
smtpd_tls_eecdh_grade = ultra
smtpd_tls_dh1024_param_file = /etc/postfix/certs/dh_2048.pem

As you can see, I enforce strong’ish ciphers/hashes by disabling aNull, MD5, and RC4.
I also setup ephemeral DH for additional encryption.  If my SSL cert were stolen, the hacker would not be able to decrypt the connection because DH encryption takes place after the first key exchange.  This is done through the smtpd_tls_dh1024/512_param_file settings.  You will want to generate your own DH files so that postfix doesn’t use the built-in DH certs.
openssl gendh -out /etc/postfix/certs/dh_2048.pem -2 -rand /dev/urandom 2048
Notice that the parameter name is smtpd_tls_dh1024_param_file, but I’m using a 2048 bit key.
As of mid-2015, SMTP clients are starting to reject TLS handshakes with primes smaller than 2048 bits.
As a side note, make sure you have restrictive permissions on the directory that contains the certs.  You don’t want that readable by everyone on the system.
Newer versions of postfix and openssl support elliptical curve cryptography.  It is enabled with the smtpd_tls_eecdh_grade parameter.  I set mine to ultra, which is substantially more cpu intensive.
I used urandom instead of random due to the length of time it takes to generate entropy.

Here is the postfix log showing that the inbound connection was encrypted when I sent myself an email from the outside (btw, aol is my spam account):
Oct  7 22:02:11 goemon postfix/smtpd[21758]: initializing the server-side TLS engine
Oct  7 22:02:12 goemon postfix/smtpd[21758]: connect from omr-m5.mx.aol.com[64.12.232.237]
Oct  7 22:02:12 goemon postfix/smtpd[21758]: setting up TLS connection from omr-m5.mx.aol.com[64.12.232.237]
Oct  7 22:02:12 goemon postfix/smtpd[21758]: omr-m5.mx.aol.com[64.12.232.237]: TLS cipher list “aNULL:-aNULL:ALL:+RC4:@STRENGTH:!aNULL”
Oct  7 22:02:12 goemon postfix/smtpd[21758]: setting up TLS connection from omr-m5.mx.aol.com[64.12.232.237]
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:before/accept initialization
Oct  7 22:02:12 goemon postfix/smtpd[21758]: omr-m5.mx.aol.com[64.12.232.237]: TLS cipher list “aNULL:-aNULL:ALL:+RC4:@STRENGTH:!aNULL”
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client hello A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write server hello A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write certificate A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:before/accept initialization
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client hello A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write server hello A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write certificate A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write key exchange A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write certificate request A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 flush data
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client certificate A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client key exchange A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read finished A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write change cipher spec A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client certificate A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read client key exchange A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 read finished A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write finished A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write change cipher spec A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 flush data
Oct  7 22:02:12 goemon postfix/smtpd[21758]: Anonymous TLS connection established from omr-m5.mx.aol.com[64.12.232.237]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 write finished A
Oct  7 22:02:12 goemon postfix/smtpd[21758]: SSL_accept:SSLv3 flush data
Oct  7 22:02:12 goemon postfix/smtpd[21758]: Anonymous TLS connection established from omr-m5.mx.aol.com[64.12.232.237]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
Oct  7 22:02:12 goemon postfix/smtpd[21758]: 905AD801E1: client=omr-m5.mx.aol.com[64.12.232.237]

As you can see, the connection was established using TLSv1 with DHE-RSA-AES256-SHA.

 

 

Here is how I setup egress TLS:
smtp_tls_cert_file = /etc/postfix/certs/mkirby.org.crt
smtp_tls_key_file = /etc/postfix/certs/mkirby.org.key
smtp_tls_CApath = /etc/postfix/certs
smtp_tls_CAfile = /etc/postfix/certs/ca.crt
smtp_tls_loglevel = 2
smtp_tls_session_cache_timeout = 360s
smtp_use_tls = yes
smtp_enforce_tls = no

I didn’t exclude any ciphers or protocols for egress because I’d rather have weak encryption rather than no encryption.
Here is the postfix log showing that the outbound connection was encrypted when I sent an email to my gmail account:
Oct  7 17:51:26 goemon postfix/qmgr[1210]: 749028075E: from=<nobody@lunari.net>, size=3218244, nrcpt=1 (queue active)
Oct  7 17:51:26 goemon postfix/smtpd[19585]: disconnect from camera.mk.lan[192.168.1.86]
Oct  7 17:51:26 goemon postfix/smtp[19589]: initializing the client-side TLS engine
Oct  7 17:51:28 goemon postfix/smtp[19589]: setting up TLS connection to gmail-smtp-in.l.google.com[74.125.201.26]:25
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: TLS cipher list “aNULL:-aNULL:ALL:+RC4:@STRENGTH”
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:before/connect initialization
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv2/v3 write client hello A
Oct  7 17:51:28 goemon postfix/smtp[19589]: setting up TLS connection to gmail-smtp-in.l.google.com[74.125.201.26]:25
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: TLS cipher list “aNULL:-aNULL:ALL:+RC4:@STRENGTH”
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:before/connect initialization
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv2/v3 write client hello A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server hello A
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=2 verify=0 subject=/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=2 verify=0 subject=/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=1 verify=1 subject=/C=US/O=Google Inc/CN=Google Internet Authority G2
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=0 verify=1 subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mx.google.com
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server certificate A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server key exchange A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server done A
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=2 verify=0 subject=/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=1 verify=1 subject=/C=US/O=Google Inc/CN=Google Internet Authority G2
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: certificate verification depth=0 verify=1 subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mx.google.com
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server certificate A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server key exchange A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write client key exchange A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write change cipher spec A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write finished A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 flush data
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server session ticket A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read finished A
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: subject_CN=mx.google.com, issuer_CN=Google Internet Authority G2, fingerprint=88:C0:85:C8:CB:96:29:8F:4E:15:11:80:C5:9A:89:0D, pkey_fingerprint=E0:32:29:21:69:38:EA:F9:B6:0C:F6:BD:86:12:16:B9
Oct  7 17:51:28 goemon postfix/smtp[19589]: Untrusted TLS connection established to gmail-smtp-in.l.google.com[74.125.201.26]:25: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server done A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write client key exchange A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write change cipher spec A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 write finished A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 flush data
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read server session ticket A
Oct  7 17:51:28 goemon postfix/smtp[19589]: SSL_connect:SSLv3 read finished A
Oct  7 17:51:28 goemon postfix/smtp[19589]: gmail-smtp-in.l.google.com[74.125.201.26]:25: subject_CN=mx.google.com, issuer_CN=Google Internet Authority G2, fingerprint=88:C0:85:C8:CB:96:29:8F:4E:15:11:80:C5:9A:89:0D, pkey_fingerprint=E0:32:29:21:69:38:EA:F9:B6:0C:F6:BD:86:12:16:B9
Oct  7 17:51:28 goemon postfix/smtp[19589]: Untrusted TLS connection established to gmail-smtp-in.l.google.com[74.125.201.26]:25: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)

 

 

 

 

SSH setup on mkirby.org

The default OpenSSH settings on Fedora are pretty secure, but I like to add a few extra features.
Below are the extra settings I have in addition to the defaults.

1) I increase the log level to verbose.  The only benefit I see is for key auditing.  If a login uses an ssh key, its fingerprint will be logged to /var/log/secure.  As you can see from the log sample below, I used a valid key to log into my server.  It will only show the fingerprint of the key I used.

May 24 17:08:31 goemon sshd[53854]: Partial password for mkirby from 192.168.1.50 port 37137 ssh2
May 24 17:08:31 goemon sshd[53854]: Found matching ECDSA key: b4:5d:9e:74:cd:c6:f9:28:17:d1:b3:d6:ca:89:8b:49
May 24 17:08:31 goemon sshd[53854]: Accepted publickey for mkirby from 192.168.1.50 port 37137 ssh2: ECDSA b4:5d:9e:74:cd:c6:f9:28:17:d1:b3:d6:ca:89:8b:49

If I try to authenticate with an invalid key, it will log the fingerprint of the key that was attempted. This can be useful when a user complains that their ssh key isn’t working. You can match the fingerprint of the key they are trying to use with the key that is setup for their account. It can also be useful if you see the fingerprint of a known compromised key.

May 24 17:19:43 goemon sshd[53968]: Failed publickey for mkirby from 192.168.1.50 port 37183 ssh2: DSA bc:5d:88:98:23:25:88:1c:81:03:26:ed:66:2f:dd:4e

To find out the fingerprint of the keys in your authorized keys file, you can use ssh-keygen like so where -l means show the fingerprint and the authorized keys file is specified after the -f. I have my ssh key in a non-standard location, so you will probably want to specify your authorized_keys file in the user’s .ssh directory.

/ # ssh-keygen -l -f /etc/ssh/keys/mkirby
521 b4:5d:9e:74:cd:c6:f9:28:17:d1:b3:d6:ca:89:8b:49 mkirby@jigen.mk.lan (ECDSA)


2) I use a non-standard location for ssh keys. I store them in the /etc/ssh/keys/ directory where the filename is the username. This is setup in /etc/ssh/sshd_config like so: AuthorizedKeysFile /etc/ssh/keys/%uThe files are read-only and owned by the user. Usually that means that a user can add a new key by changing the permissions or forcing to write with an editor. On my system I use chattr to make the file immutable, which prevents the user from changing/adding/deleting their keys file even if they try to force write or change the file permissions. Only root can set the immutable flag on the files, so the user cannot undo the flag. The user has absolutely no control over their authorized ssh keys.

3) I setup 2-factor authentication on my ssh server so that both a password and an ssh key are required. If either are incorrect, the user will not authenticate.Here is the setting I have in sshd_config: AuthenticationMethods password,publickey keyboard-interactive,publickeyThat means that the server will first ask for a password and, if the password is valid, ask for the ssh key. Most documentation will say to use the opposite direction, where the key is first and then the password. I prefer password first because a brute-force password attack will not detect that an ssh key is also required. This not only makes the attack completely futile, but the attacker will just assume that none of the passwords were successful (even if they were). If the key were prompted before the password, the attacker would know that a brute-force password attack would fail. This method creates visibility for attacks and wastes time for the attacker (unless the attacker is reading this blog).Here is what my ssh login looks like. Notice that it says “Authenticated with partial success.” after prompting for the password. If I used the other authentication order, it would show that before prompting for the password. An attacker would see that and know that 2-factor is enforced.


# ssh goemon
mkirby@goemon’s password:
Authenticated with partial success.
Last failed login: Sat May 24 18:05:50 CDT 2014 from jigen.mk.lan on ssh:notty
There were 2 failed login attempts since the last successful login.
Last login: Sat May 24 17:40:17 2014 from jigen.mk.lan
[ ?0 goemon mkirby !636 &0 18:05:50 ]
~ #

The only problem with this setup is that it triggers 2 failed login attempts everytime I login. Just a minor annoyance. It still increments on failed password attempts, so I just need to be vigilant for greater than 2 failed logins.

4) Logins are restricted to a special group. By default, the ssh service will allow any user to authenticate. I set up my server so that only members of the wheel group are permitted to login. This prevents login access to non-user accounts that may have been compromised. To enable this, I put AllowGroups sshusers in the sshd_config. The only member of that group is my personal account.

5) Forwarding, tunneling, etc are disabled for obvious reasons.
Here are the settings I have in sshd_config:


AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
PermitTunnel no


6) I enforce strong ciphers and hmacs. By default, many of the weaker ciphers and hmacs are enabled. The stronger ciphers are usually chosen first, but I like to enforce the strong ones. Some security vulnerability scans may flag the md5 hmacs as insecure, which is how I discovered these settings. I only enable the ciphers/hmacs I need to ssh with the OpenSSH, Putty, and SecureCRT clients. Putty and SecureCRT can’t use the newer and stronger ciphers/hmacs, so the weaker ones are listed after the stronger, more preferred.
Here are the settings in sshd_config:

ciphers aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha1


7) I use 521-bit ECDSA keys for authentication. Prior to the newer OpenSSH, I used 4096-bit RSA keys. The default key is 1024-bit DSA. If you don’t know the difference between these key types, Google is your friend. The possible bit lengths for ECDSA is 256, 384, and 521. I’m no mathematician, but the 521-bit looks odd. At first I thought it was a typo of 512, but it is not. From what I’ve read, ECDSA is faster and arguably more secure than RSA even at a smaller key size. ECDSA uses elliptical curves and RSA uses primes. That’s as much as I want to know as far as the mathematics.
Here is how to create a 521-bit ECDSA key:

ssh-keygen -t ecdsa -b 521


8) I also use a 521-bit ecdsa hostkey and removed the other hostkeys. It will regenerate the rsa hostkey automatically, but it will only use the ecdsa hostkey.
Here is how to create a 521-bit ECDSA hostkey:

ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key

Here are the settings in sshd_config:

HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_dsa_key


9) I restrict network access through TCP wrappers. Only the local network is allowed ssh access. There is already 2 firewalls in front of this webserver, so this only adds another layer of security. This is configured through /etc/hosts.allow and /etc/hosts.deny. I also have TCP wrappers spawn a logger to record some information about the connection. This is especially important for the hosts.deny as it can help with troubleshooting.
Here is my /etc/hosts.deny. I deny everything by default:

ALL: ALL: spawn (logger -p daemon.notice “TCPD_DENY %d from %a PID %p Service %s”) &

Here is my /etc/hosts.allow:

rpcbind: 192.168.0.0/16
sshd: 192.168.1.0/24: spawn ( logger -p daemon.notice “TCPD_ALLOW %d from %a PID %p Service %s”) &


10) I set PermitRootLogin no in sshd_config so that the root account cannot be logged into directly through ssh. To gain root access, I must log in with my personal account and sudo su to root.

Encrypted swap on mkirby.org

I encrypt the swap on all my Linux systems.  Processes may contain sensitive information, such as keys and passwords, that are usually stored in memory as that program runs.  There is the potential for that sensitive data to be written to swap.  On my systems, my primary concern is the passphrase I use with TrueCrypt.  There is a slim possibility that a thief could steal my systems and decrypt my drives by analysing the swap space.
Setting up swap encryption is very easy.

  1. Turn off swap with ‘swapoff -a
  2. Setup /etc/crypttab for encryption with a randomly generated key from /dev/urandom. You can use /dev/random for better random entropy, but it will extend the bootup time by several minutes. Read the urandom manual page if you don’t know the difference. Make sure you replace /dev/mapper/fedora_goemon-swap with your swap partition.
    cryptoswap /dev/mapper/fedora_goemon-swap /dev/urandom cipher=aes-cbc-essiv:sha256,hash=sha256,swap
  3. Add the cryptoswap to /etc/fstab
    /dev/mapper/cryptoswap swap swap defaults 0 0
  4. You can either reboot and you’re all set or you can activate the encrypted swap immediately:
    cryptsetup -d /dev/urandom create cryptoswap /dev/mapper/fedora_goemon-swap
    swapon -a

OSSEC on mkirby.org

I run OSSEC HIDS/FIM at mkirby.org.
If you don’t know what that is, goto http://ossec.net.
The goal of this post isn’t to tell you why I run it, or how awesome it is. I’m going to tell you how my environment is configured.

The mkirby.org webserver runs the OSSEC agent and is monitored/configured by the OSSEC server, which is on a separate network filtered by a firewall.  I use the client/server configuration because it is more secure than the local-only configuration.  With local-only, a badguy who gains root access can tamper with the configuration/alerting/database to hide their tracks.  With client/server, everything is handled and protected on the server.
The OSSEC server pushes the configuration to the agent and records the alerts/logs, from the agent, in Splunk. I wrote a script that reads /var/ossec/logs/alerts/alerts.log and reformats into key=value pairs for easy consumption in Splunk.  The script is called ossec2splunk.pl is hosted at SourceForge at https://github.com/mtkirby/ossec2splunk.   I also wrote a script to email me the FIM alerts.  It is also hosted at SourceForge in the ossec2splunk project.  The script is called osseccompactor.pl.  It is written in a similar style as the LogCompactor scripts I wrote and hosted at SplunkBase.  The osseccompactor script formats the FIM alerts into an html table and emails it to me.  The script was written for an environment with thousands of servers, such as a cloud.  The email shows the names of the modified files, the permissions/ownership (if they changed), and the contextual difference.  It does not show the checksums, file sizes, and hostnames.  That information just bloats the email alert with useless data.  One of my biggest gripes with FIMs is that they show the checksum of the file, which doesn’t tell me anything other than “something changed”.  With my osseccompactor email alert, I can see what in the file changed, what lines were removed/added.  The osseccompactor email also contains links that go to Splunk so that I can easily dig deeper into an alert.  If you want to run ossec2splunk, you will need to setup a Splunk Search to run a wrapper shell script for osseccompactor.  If you setup LogCompactor, then you’ll be half-way there.

In Splunk, I setup a search macro called ‘ossectable’.  I can run a search query and pipe it through `ossectable` to format the output as a table.   Here is the search macro: table Integrity_checksum_changed_for hostname New_sha1sum_is oldsize newsize oldowner newowner oldgroup newgroup oldpermissions newpermissions deleted diffrm diffadd |sort Integrity_checksum_changed_for ASC

 

To enable remote commands from the manager, I set logcollector.remote_commands=1 in etc/internal_options.conf.  This allows the scripts that block IPs to run.  When the logcollector on the manager finds suspicious activity in the system logs, it will tell the agent to block the offending IP.

 

Below is my agent.conf.
A few things to note that many admins don’t think about are:

  1. The “report_changes” on etc, cron,  and ssh authorized_keys files.  This will show the contextual change that was made to the files.
  2. The alert_new_files is enabled.  By default, it doesn’t alert on new files.
  3. I don’t do real-time checking on file changes because it isn’t capable of reporting new files.
  4. I changed ‘netstat -an’ to ‘netstat -peanut’.  The peut flags add the process name/id, userid, and other info to the output.  I always write it as ‘-peanut’ because it’s easy to remember.  These flags are only available in Linux.  For BSD/other, I would use ‘lsof -Pni’.
  5. The file change alerts do not tell you who made the change.  For that, see my posting about the Linux audit system and how I set it up to log shell/file activity to Splunk

<agent_config>
  <global>                                                                                                           
    <email_notification>no</email_notification>                                                                      
    <email_to>ossecwatch@mkirby.org</email_to>                                                                         
    <smtp_server>127.0.0.1</smtp_server>                                                                             
    <email_from>ossecm@mkirby.org</email_from>                                                                       
  </global>                                                                                                          
                                                                                                                     
  <rules>                                                                                                            
    <include>rules_config.xml</include>                                                                              
    <include>pam_rules.xml</include>                                                                                 
    <include>sshd_rules.xml</include>                                                                                
    <include>syslog_rules.xml</include>                                                                              
    <include>named_rules.xml</include>                                                                               
    <include>smbd_rules.xml</include>                                                                                
    <include>proftpd_rules.xml</include>                                                                             
    <include>hordeimp_rules.xml</include>                                                                            
    <include>wordpress_rules.xml</include>                                                                           
    <include>web_rules.xml</include>                                                                                 
    <include>web_appsec_rules.xml</include>                                                                          
    <include>apache_rules.xml</include>                                                                              
    <include>mysql_rules.xml</include>                                                                               
    <include>firewall_rules.xml</include>                                                                            
    <include>postfix_rules.xml</include>                                                                             
    <include>dovecot_rules.xml</include>                                                                             
    <include>spamd_rules.xml</include>                                                                               
    <include>ossec_rules.xml</include>                                                                               
    <include>local_rules.xml</include>                                                                               
  </rules>                                                                                                           
                                                                                                                     
  <syscheck>                                                                                                         
    <frequency>7200</frequency>                                                                                      
    <disabled>no</disabled>                                                                                          
    <alert_new_files>yes</alert_new_files>                                                                           
    <scan_on_start>yes</scan_on_start>                                                                               
    <auto_ignore>no</auto_ignore>                                                                                    
                                                                                                                     
    <!– Directories to check  (perform all possible verifications) –>                                              
    <directories realtime=”no” report_changes=”yes” check_all=”yes”>/etc,/var/spool/cron,/var/spool/at,/var/named/chroot/etc,/var/ossec/etc,/root/.ssh/authorized_keys,/var/ossec/.ssh/authorized_keys,/home/*/.ssh/authorized_keys</directories>                                                      
    <directories realtime=”no” check_all=”yes”>/opt,/var/www,/boot,/lib,/lib64,/usr,/var/ossec/bin</directories>     
    <directories realtime=”no” check_owner=”yes” check_group=”yes” check_perm=”yes”>/dev</directories>

                                                                                                                     
    <!– Files/directories to ignore –>                                                                             
    <ignore>/etc/mtab</ignore>                                                                                       
    <ignore>/etc/mnttab</ignore>                                                                                     
    <ignore>/etc/hosts.deny</ignore>                                                                                 
    <ignore>/etc/mail/statistics</ignore>                                                                            
    <ignore>/etc/random-seed</ignore>                                                                                
    <ignore>/etc/adjtime</ignore>                                                                                    
    <ignore>/etc/httpd/logs</ignore>                                                                                 
    <ignore>/etc/utmpx</ignore>                                                                                      
    <ignore>/etc/wtmpx</ignore>                                                                                      
    <ignore>/etc/cups/certs</ignore>                                                                                 
    <ignore>/etc/dumpdates</ignore>                                                                                  
    <ignore>/etc/svc/volatile</ignore>
    <ignore>/etc/selinux/targeted</ignore>
    <ignore>/etc/ld.so.cache</ignore>
    <ignore>/etc/webmin/system-status</ignore>
    <ignore>/opt/splunk/var</ignore>
  </syscheck>

  <rootcheck>
    <frequency>7200</frequency>
    <disabled>no</disabled>  
    <rootkit_files>/var/ossec/etc/shared/rootkit_files.txt</rootkit_files>
    <rootkit_trojans>/var/ossec/etc/shared/rootkit_trojans.txt</rootkit_trojans>
    <system_audit>/var/ossec/etc/shared/system_audit_rcl.txt</system_audit>
  </rootcheck>

  <global>
    <white_list>127.0.0.1</white_list>
    <white_list>^localhost.localdomain$</white_list>
    <white_list>192.168.0.15</white_list>
    <white_list>8.8.8.8</white_list>
    <white_list>192.168.1.50</white_list>
    <white_list>192.168.1.10</white_list>
    <white_list>192.168.1.1</white_list>
    <white_list>192.168.1.4</white_list>
    <white_list>192.168.1.5</white_list>
  </global>

  <alerts>
    <log_alert_level>1</log_alert_level>
    <email_alert_level>7</email_alert_level>
  </alerts>

  <command>
    <name>host-deny</name>
    <executable>host-deny.sh</executable>
    <expect>srcip</expect>
    <timeout_allowed>yes</timeout_allowed>
  </command>  

  <command>
    <name>firewall-drop</name>
    <executable>firewall-drop.sh</executable>
    <expect>srcip</expect>
    <timeout_allowed>yes</timeout_allowed>
  </command>  

  <command>
    <name>restart-ossec</name>
    <executable>restart-ossec.sh</executable>
    <expect></expect>
  </command>
                  

  <command>
    <name>route-null</name>
    <executable>route-null.sh</executable>
    <expect>srcip</expect>
    <timeout_allowed>yes</timeout_allowed>
  </command>

  <!– Active Response Config –>
  <active-response>
    <!– This response is going to execute the host-deny
       – command for every event that fires a rule with
       – level (severity) >= 6.
       – The IP is going to be blocked for  600 seconds.
      –>
    <command>host-deny</command>
    <location>local</location>
    <level>6</level>
    <timeout>600</timeout>
  </active-response>

  <active-response>
    <!– Firewall Drop response. Block the IP for
       – 600 seconds on the firewall (iptables,
       – ipfilter, etc).
      –>
    <command>firewall-drop</command>
    <location>local</location>
    <level>6</level>
    <timeout>600</timeout>    
  </active-response>  

  <!– Files to monitor (localfiles) –>

  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/messages</location>
  </localfile>

  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/secure</location>
  </localfile>

  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/maillog</location>
  </localfile>

  <localfile>
    <log_format>apache</log_format>
    <location>/var/log/httpd/*log</location>
  </localfile>

  <localfile>
    <log_format>full_command</log_format>
    <command>netstat -peanut |grep LISTEN |grep -v 127.0.0.1 | sort</command>
  </localfile>
</agent_config>


	

WordPress on mkirby.org

This blog is running WordPress.  I use the packages from Fedora.  I also have a cronjob that auto-updates the packages as soon as they are available.  This reduces the potential window of opportunity a hacker could access a vulnerability on my blog.  You should also ready my postings on my PHP, Apache, and IPTables configurations.

 

Since the package can be auto-updated, I have a script that runs every hour to re-run the security modifications I’ve made to wordpress.  If/when WordPress is auto-patched, my script will redo the security fixes.  Here are the commands I have scripted.

  • By default, /usr/share/wordpress is owned by the wordpress user account. This is required if you want to add new plugins or use it as a shared account to update static content. I don’t need either of those, so I chown’d it to root. I wouldn’t be able to download plugins through the WordPress dashboard anyways since my IPTables firewall prohibits the Apache webserver from connecting to anything. See my posting on my IPTables configuration for more details.
    chown -R root /usr/share/wordpress
  • By default, /etc/wordpress is world-readable. It contains a file called wp-config.php, which contains the database username/password along with the auth keys and salts. That is horrible default ownership/permissions. I chgrp’d to apache and removed world-readable permissions on the directory.
    chgrp apache /etc/wordpress
    chmod 750 /etc/wordpress
  • I am running the “twentytwelve” theme, which displays information about WordPress on the blog pages. I removed that by adding a site-info tag to the CSS.
    grep site-info /usr/share/wordpress/wp-content/themes/twentytwelve/style.css >/dev/null 2>&1 || echo “
    .site-info {
    display: none;
    }” >> /usr/share/wordpress/wp-content/themes/twentytwelve/style.css

 

I restricted access to the admin area and the login page.  If I allowed for users to sign-up to my site, I would allow the login page, but restrict the admin area.
<Location /mkblog/wp-admin>
     Order deny,allow
     Allow from 192.168.1.0/24
     Deny from all
     require all granted
</Location>
<Location /mkblog/wp-login.php>
     Order deny,allow
     Allow from 192.168.1.0/24
     Deny from all
     require all granted
</Location>

 

I installed the Better WP Security plugin and followed some of what it recommended.
First, I allowed it to remove the default admin user, which also removed user id 1.
In the “tweaks” section, I enabled the following:
Protect Files – Prevent public access to readme.html, readme.txt, wp-config.php, install.php, wp-includes, and .htaccess. These files can give away important information on your site and serve no purpose to the public once WordPress has been successfully installed.
Disable Directory Browsing – Prevents users from seeing a list of files in a directory when no index file is present.
Filter Request Methods – Filter out hits with the trace, delete, or track request methods.
Filter Suspicious Query Strings – Filter out suspicious query strings in the URL. These are very often signs of someone trying to gain access to your site but some plugins and themes can also be blocked.
Remove WordPress Generator Meta Tag – Removes the meta tag from your sites header. This process hides version information from a potential attacker making it more difficult to determine vulnerabilities.
Remove wlwmanifest header – Removes the Windows Live Writer header. This is not needed if you do not use Windows Live Writer or other blogging clients that rely on this file.
Remove EditURI header – Removes the RSD (Really Simple Discovery) header. If you don’t integrate your blog with external XML-RPC services such as Flickr then the “RSD” function is pretty much useless to you.
Hide Theme Update Notifications – Hides theme update notifications from users who cannot update themes. Please note that this only makes a difference in multi-site installations.
Hide Plugin Update Notifications – Hides plugin update notifications from users who cannot update themes. Please note that this only makes a difference in multi-site installations.
Hide Core Update Notifications – Hides core update notifications from users who cannot update themes. Please note that this only makes a difference in multi-site installations.
Enable strong password enforcement – Enforce strong passwords for all users with at least the role specified below.
Remove WordPress Login Error Messages – Prevents error messages from being displayed to a user upon a failed login attempt.
Display random version number to all non-administrative users – Displays a random version number to visitors who are not logged in at all points where version number must be used and removes the version completely from where it can.

 

I setup a rewrite rule to redirect 404 pages back to the browser.  When an attacker scans my site for vulnerable software, every page that does not exist is redirected back to them.  This doesn’t necessarily stop the attack, but it’s fun to mess with the hackers:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !/wp-admin
RewriteRule .* http://127.0.0.1%{REQUEST_URI}?GIVE_IT_UP=LOL [R,L,QSA]
The !-f matches if the requested file does not exist.
The !-d matches if the requested directory does not exist.
The !-l matches if the requested file could be a link and does not exist.
When all conditions are met, the rewrite rule sends a 302 redirect with a Location header that tells the browser that the requested file was moved to their own computer.

 

The Better WP Security plugin tries to make a few changes to the .htaccess file, which I disabled through chown’ing the directory to root.  Instead, I took what it wanted to put in the .htaccess file and put it directly into the directory stanza for wordpress in my http config file in /etc/.     Below are the additions.  I changed the recommended rejected rule to a redirect to localhost.

RewriteRule ^wp-admin/includes/ – [F,L]
RewriteRule !^wp-includes/ – [S=3]
RewriteCond %{SCRIPT_FILENAME} !^(.*)wp-includes/ms-files.php
RewriteRule ^wp-includes/[^/]+\.php$ – [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php – [F,L]
RewriteRule ^wp-includes/theme-compat/ – [F,L]

RewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|TRACK) [NC]
#RewriteRule ^(.*)$ – [F,L]
RewriteRule .* http://127.0.0.1%{REQUEST_URI}?GIVE_IT_UP=LOL [R,L,QSA]

RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} ^(.*)wp-comments-post\.php*
RewriteCond %{HTTP_REFERER} !^(.*).*
RewriteCond %{HTTP_REFERER} !^http://jetpack\.wordpress\.com/jetpack-comment/ [OR]
RewriteCond %{HTTP_USER_AGENT} ^$
#RewriteRule ^(.*)$ – [F,L]
RewriteRule .* http://127.0.0.1%{REQUEST_URI}?GIVE_IT_UP=LOL [R,L,QSA]

RewriteCond %{QUERY_STRING} \.\.\/ [NC,OR]
RewriteCond %{QUERY_STRING} ^.*\.(bash|git|hg|log|svn|swp|cvs) [NC,OR]
RewriteCond %{QUERY_STRING} etc/passwd [NC,OR]
RewriteCond %{QUERY_STRING} boot\.ini [NC,OR]
RewriteCond %{QUERY_STRING} ftp\:  [NC,OR]
RewriteCond %{QUERY_STRING} http\:  [NC,OR]
RewriteCond %{QUERY_STRING} https\:  [NC,OR]
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|%3D) [NC,OR]
RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>|ê|”|;|\?|\*|=$).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(&#x22;|&#x27;|&#x3C;|&#x3E;|&#x5C;|&#x7B;|&#x7C;).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(%24&x).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(127\.0).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(globals|encode|localhost|loopback).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(request|select|concat|insert|union|declare).* [NC]
RewriteCond %{QUERY_STRING} !^loggedout=true
RewriteCond %{QUERY_STRING} !^action=rp
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in_.*$
RewriteCond %{HTTP_REFERER} !^http://maps\.googleapis\.com(.*)$
#RewriteRule ^(.*)$ – [F,L]
RewriteRule .* http://127.0.0.1%{REQUEST_URI}?GIVE_IT_UP=LOL [R,L,QSA]

<files readme.html>
Order allow,deny
Deny from all
</files>

<files readme.txt>
Order allow,deny
Deny from all
</files>

<files install.php>
Order allow,deny
Deny from all
</files>

<files wp-config.php>
Order allow,deny
Deny from all
</files>

PHP security on mkirby.org

PHP has been plagued with vulnerabilities since the day it was invented.  It has a horrible track-record not only for the core, but for almost everything that runs it.

The default settings don’t offer any protection, so here is what I did to make my website run more secure with PHP:

Change display_errors to off so that php errors are not displayed on the browser.

Also, turn on error logging so that the developers can debug their code.
log_errors = On
error_log = phperrors_log

Make sure register_globals is disabled. This will prevent hackers from modifying variables that they should not be allowed to.  This has been the default setting in PHP for the past few years.
register_globals = Off

Disable the ability to open files via a url.  Hackers can exploit PHP with a remote file inclusion attack to execute their own php script on a target host.  This setting will prohibit that attack.
allow_url_fopen = Off
allow_url_include = Off

Change expose_php to off so that php version information is not displayed in the header.
expose_php = Off

Disable dangerous functions. Some of these functions may be used, so plan accordingly.
By disabling these functions, remote php shell exploits are rendered useless.
disable_functions=exec, passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source, phpinfo, fopen_with_path, dbmopen, dbase_open, putenv, move_uploaded_file, chdir, mkdir, rmdir, chmod, rename, filepro, filepro_rowcount, filepro_retrieve, posix_mkfifo

Configure PHP to force the browser to use a cookie for the session:
session.use_only_cookies = 1

URL-based session management is disabled by default.  Double-check with:
session.use_trans_sid = 0

Disallow javascript to interact with cookies (prevents XSS cookie stealing)
session.cookie_httponly = 1

In httpd.conf, change
AddType application/x-httpd-php .php4 .php3 .phtml .php
to
AddType application/x-httpd-php .php5 .php4 .php3 .phtml .php .inc .class
This will prevent hackers from viewing .inc and .class files that contain php code.

Apache security on mkirby.org

I use the default Apache httpd packages from Fedora.   There are several changes that I make to the default configuration.

I have a logformat that is easily consumable with Splunk by using key=value pairs.  I added several values to be logged in addition to the default logs.  The x-forwarded-for, via, and clientip values help me determine the origin of the request if it came through a proxy.
LogFormat “request-line=\”%r\” remote-host=%h remote-ip=%a proxyfor=%{PROXYFOR}i x-forwarded-for=%{X-Forwarded-For}i via=%{VIA}i clientip=%{ClientIP}i canonical-port=%p response-size=%B request-time-taken=%D request-protocol=%H keepalive=%k request-method=%m process-id=%P query-string=%q handler=%R http-status-code=%s  http-request-time=\”%t\” request-time-taken=%T remote-user=%u http-request-url=%U servername-canonical=%v servername=%V http-connection-status=%X http-bytes-received=%I http-bytes-sent=%O http-referer=\”%{Referer}i\” http-user-agent=\”%{User-Agent}i\” ssl_protocol=\”%{SSL_PROTOCOL}x\” ssl_cipher=\”%{SSL_CIPHER}x\”” extended

My site uses vhosts with a default “null” vhost.  This null vhost is the first listed in the configuration.  Apache will choose the first vhost listed when it cannot match a servername to a valid vhost.  This is the vhost that is chosen when a browser hits the site by IP rather than hostname.  I do the same for my default https vhost.  For https, I use a self-signed SSL certificate that does not reveal any of the vhosts I host.  The certificate simply uses a commonname of NULL.  My null vhost is configured to use the directory /var/www/null, which is owned by root and chmod’d 000.  Furthermore, any request will be redirected with a rewrite rule back to the browser’s localhost.  The reason for this is to annoy scanners and hackers.  Most of the scanners out in the wild that are run by script kiddies will probe by IP address rather than server or domain names.  Those scanners won’t find this blog or anything else I host.  I don’t know if the redirect causes the scanners to scan themselves, but I like to hope so.  Here is the rewrite rule that I use:
RewriteRule .* http://127.0.0.1%{REQUEST_URI}?YOU_GOT_HACKED=LOL [R,L,QSA]
I append the “YOU_GOT_HACKED=LOL” to the redirect.  If the request came from a compromised webserver that is being used by a hacker to scan (usually to hide their tracks), then hopefully the scanner will follow the redirect and the admin of the compromised webserver will see this message in the logs.

For my other vhosts and directories, I have a similar redirect that is done when the requested file, directory, or symlink does not exist.  Here is the config:
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-l
        RewriteCond %{REQUEST_FILENAME} !/wp-admin
        RewriteRule .* http://127.0.0.1%{REQUEST_URI}?YOU_GOT_HACKED=LOL [R,L,QSA]

 

By default, the Fedora httpd package sets up a user called apache with /sbin/nologin as the shell.  Back when I compiled Apache from source, I would make sure that the Apache user had an invalid shell and an invalid home directory.  I also make sure that the Apache user does not have write access to anything that is not necessary for the site to run.

The SSL certificates for mkirby.org are only readable by root.  When Apache starts up, the certificates are ready before it forks and runs as the Apache user.

I disable weak SSL ciphers and only allow for SSLv3 and TLSv1-1.2.  My SSL configuration has earned me an “A” rating from Qualys SSL Labs.  You can scan my site with Qualys by clicking here.  The scan will take about a minute to run.
Below is the ciphersuites I setup.  Notice that I enabled ephemeral Diffie-Hellman.  This allows for forward secrecy.  In addition to the RSA asymmetric key exchange, it performs another on-the-fly Diffie-Hellman key exchange before performing the symmetric key exchange.  This means that if my private SSL key were stolen, the hacker would still be unable to decrypt my https traffic.  I also have SSLHonorCipherOrder enabled so that the Apache server will attempt to use the strongest ciphers first and negotiate the most secure cipher supported by the browser.  As of October 2013, all browsers fail to support ECDHE (Elliptial-Curve Diffie-Hellman Ephemeral), but do support DHE (Diffie-Hellman Ephemeral).  My server will be able to support the ECDHE when browser vendors decide to support it.
SSLCipherSuite ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA:HIGH:!SSLv2:!MD5:!aNULL:!eNULL:!NULL:!ADH:!AESGCM:!MEDIUM:!LOW
SSLProtocol -ALL +SSLv3 +TLSv1 +TLSv1.1 +TLSV1.2

I also disable SSL renegotiation to mitigate known Man-In-The-Middle attacks.  The SSLv3 and TLSv1.0 protocols have a known MITM attack when the server supports SSL renegotiation.  I disabled SSL renegotiation with a simple setting: SSLInsecureRenegotiation off
A cache management feature for Apache makes use of an entity tag (ETag) header. When this option is enabled and a request is made for a document relating to a file, an ETag response header is returned containing various file attributes for caching purposes. ETag information allows subsequent file requests to contain specific information, such as the file’s inode number.  I modified the etag setting to not disclose the inode number with this setting:  FileETag MTime Size

I enabled SSL Stapling and configured it with the following settings:
     SSLUseStapling on
     SSLStaplingResponderTimeout 5
     SSLStaplingReturnResponderErrors off
     SSLStaplingCache “shmcb:/var/run/httpd/ocsp(128000)”
SSL Stapling allows the webserver to cache it’s cert validity by connecting to the CA’s revocation list server and providing the results to the browser. In the past, the browser was supposed to connect to the revocation list servers for each cert it came across, which added latency.
Since I block everything egress by default, I had to make an exception for the apache user to connect to the CA revocation site.

I also set a X-Frame-Options header to “SAMEORIGIN” for clickjacking protection.  This means that any xframes on a remote that reference my site will be rejected as the xframe did not originate from my site.
There are three available options for this header: deny – no rendering within a frame, sameorigin – no rendering if origin mismatch, allow-from: URL – allow rendering frame if loaded from URL.
More details are available at https://www.owasp.org/index.php/List_of_useful_HTTP_headers
     Header always append X-Frame-Options SAMEORIGIN
     Header always append Frame-Options SAMEORIGIN

I also set the X-XSS-Protection header to “1; mode=block” to mitigate against XSS.
This header enables a Cross-site scripting (XSS) filter built into most recent web browsers. It’s usually enabled by default anyway, so role of this headers is to re-enable for this particular website if it was disabled by the user.
More details are available at https://www.owasp.org/index.php/List_of_useful_HTTP_headers
     Header always append X-XSS-Protection “1; mode=block”

I also set the  X-Content-Type-Options header to “nosniff”.
The only defined value, “nosniff”, prevents Internet Explorer and Google Chrome from MIME-sniffing a response away from the declared content-type. This also applies to Google Chrome, when downloading extensions. This reduces exposure to drive-by download attacks and sites serving user uploaded content that, by clever naming, could be treated by MSIE as executable or dynamic HTML files.
More details are available at https://www.owasp.org/index.php/List_of_useful_HTTP_headers
Header always append X-Content-Type-Options nosniff

I disabled the TRACE method by adding “TraceEnable off” in the default directory directive.  The TRACE/TRACK methods are only used for debugging and should never be allowed on an external-facing webserver.

I also disabled htaccess overrides with “AllowOverride None“.  This is added per directory stanza in the configs.  This disables the use of .htaccess which means that only the root-owned Apache config files can determine the access rules for each directory.  The .htaccess override method can be exploited to gain access to restricted areas.  This prohibits its use.

Another change I make per-directory is to remove “Indexes” and “MultiViews” for the Options directive.  By default, /icons allows for indexes.  I don’t have any directories on my site that should be browsable by index or multi-language.  Almost every directory stanza I have contains the “Options None” directive.
By default, an alias to the directory where the Apache manual is set.  I removed this alias as there is no need for visitors to view Apache documentation on my site.

There are a few dozen modules that are included in the default configuration of Apache.  I have disabled the modules that I don’t use.  I recommend that anyone running an Apache server start their security lock-down by disabling the following modules:  mod_userdir mod_autoindex mod_dav mod_dav_fs mod_speling mod_proxy* mod_asis mod_cern_meta.

To hide the Apache version, I set “ServerSignature Off” and “ServerTokens Prod“.
The ServerSignature will disable webserver version advertisement on error pages.
The ServerTokens will change the “Server” banner so that it only states “Apache” rather than the version and some module version information.

I have server-status and server-info enabled, however I have changed the names and they are only available internally with this setting:
<Location /secret-status>
Order deny,allow
Deny from all
Allow from 192.168.0.0/16 209.87.176.0/24
</Location>

I limit the HTTP methods to only allow GET POST OPTIONS and HEAD
<Location />
AllowMethods GET POST OPTIONS HEAD
</Location>

I hide all of the default error pages with ErrorDocument.  This is very important for 500 error pages as they may contain error information that could be useful to a hacker.  I take it a step further and setup a simple “error” string for anything that could go wrong.
ErrorDocument 400 “error”
ErrorDocument 401 “error”
ErrorDocument 402 “error”
ErrorDocument 403 “error”
ErrorDocument 404 “error”
ErrorDocument 405 “error”
ErrorDocument 406 “error”
ErrorDocument 407 “error”
ErrorDocument 408 “error”
ErrorDocument 409 “error”
ErrorDocument 410 “error”
ErrorDocument 411 “error”
ErrorDocument 412 “error”
ErrorDocument 413 “error”
ErrorDocument 414 “error”
ErrorDocument 415 “error”
ErrorDocument 500 “error”
ErrorDocument 501 “error”
ErrorDocument 502 “error”
ErrorDocument 503 “error”
ErrorDocument 504 “error”
ErrorDocument 505 “error”
ErrorDocument 506 “error”

When I create users in Apache, I use bcrypt with a value of 15 for the computing time.
htpasswd -B -C 15 -c /etc/httpd/.htpasswd username

I often use self-signed ssl certs on my internal servers. I don’t use the documented settings in OpenSSL. I add a few extra security features, such as aes256 cipher, sha512 message digest, and 4096 bits. I also tell OpenSSL to specifically use /dev/random rather than relying on it’s internal rng. I set the expiration date to 10 years unless I want to have my cert signed by a 3rd party CA.
Here is how I generate my certs:
$ openssl genrsa -rand /dev/random -aes256 -out server.key.pw 4096
$ openssl rsa -in server.key.pw -out server.key
$ openssl req -rand /dev/random -new -sha512 -key server.key -out server.csr
$ openssl genrsa -rand /dev/random -aes256 -out ca.key.pw 4096

If I want a 3rd party CA to sign my cert, I stop here and submit it. Otherwise, continue to build the CA and sign the server cert
$ openssl genrsa -rand /dev/random -aes256 -out ca.key.pw 4096
$ openssl rsa -in ca.key.pw -out ca.key
$ openssl req -rand /dev/random -new -sha512 -x509 -days 3650 -key ca.key -out ca.crt
$ cat /dev/null > ca.db.index
$ echo 01 > ca.db.serial
$ cat >ca.config <<EOT
[ ca ]
default_ca = CA_own
[ CA_own ]
dir = .
certs = \$dir
new_certs_dir = \$dir
database = \$dir/ca.db.index
serial = \$dir/ca.db.serial
RANDFILE = /dev/random
certificate = \$dir/ca.crt
private_key = \$dir/ca.key
default_days = 3650
default_crl_days = 30
default_md = sha512
preserve = no
policy = policy_anything
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
EOT
$ openssl ca -config ca.config -out server.crt -infiles server.csr